##// END OF EJS Templates
release: update copyright year to 2018
marcink -
r2487:fcee5614 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,63 +1,63 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22
22
23 RhodeCode, a web based repository management software
23 RhodeCode, a web based repository management software
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 """
25 """
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import platform
29 import platform
30
30
31 VERSION = tuple(open(os.path.join(
31 VERSION = tuple(open(os.path.join(
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33
33
34 BACKENDS = {
34 BACKENDS = {
35 'hg': 'Mercurial repository',
35 'hg': 'Mercurial repository',
36 'git': 'Git repository',
36 'git': 'Git repository',
37 'svn': 'Subversion repository',
37 'svn': 'Subversion repository',
38 }
38 }
39
39
40 CELERY_ENABLED = False
40 CELERY_ENABLED = False
41 CELERY_EAGER = False
41 CELERY_EAGER = False
42
42
43 # link to config for pyramid
43 # link to config for pyramid
44 CONFIG = {}
44 CONFIG = {}
45
45
46 # Populated with the settings dictionary from application init in
46 # Populated with the settings dictionary from application init in
47 # rhodecode.conf.environment.load_pyramid_environment
47 # rhodecode.conf.environment.load_pyramid_environment
48 PYRAMID_SETTINGS = {}
48 PYRAMID_SETTINGS = {}
49
49
50 # Linked module for extensions
50 # Linked module for extensions
51 EXTENSIONS = {}
51 EXTENSIONS = {}
52
52
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 __dbversion__ = 85 # defines current db version for migrations
54 __dbversion__ = 85 # defines current db version for migrations
55 __platform__ = platform.system()
55 __platform__ = platform.system()
56 __license__ = 'AGPLv3, and Commercial License'
56 __license__ = 'AGPLv3, and Commercial License'
57 __author__ = 'RhodeCode GmbH'
57 __author__ = 'RhodeCode GmbH'
58 __url__ = 'https://code.rhodecode.com'
58 __url__ = 'https://code.rhodecode.com'
59
59
60 is_windows = __platform__ in ['Windows']
60 is_windows = __platform__ in ['Windows']
61 is_unix = not is_windows
61 is_unix = not is_windows
62 is_test = False
62 is_test = False
63 disable_error_handler = False
63 disable_error_handler = False
@@ -1,542 +1,542 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import inspect
21 import inspect
22 import itertools
22 import itertools
23 import logging
23 import logging
24 import types
24 import types
25 import fnmatch
25 import fnmatch
26
26
27 import decorator
27 import decorator
28 import venusian
28 import venusian
29 from collections import OrderedDict
29 from collections import OrderedDict
30
30
31 from pyramid.exceptions import ConfigurationError
31 from pyramid.exceptions import ConfigurationError
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34 from pyramid.httpexceptions import HTTPNotFound
34 from pyramid.httpexceptions import HTTPNotFound
35
35
36 from rhodecode.api.exc import (
36 from rhodecode.api.exc import (
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.apps._base import TemplateArgs
39 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.auth import AuthUser
40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
41 from rhodecode.lib.ext_json import json
41 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.utils2 import safe_str
42 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.lib.plugins.utils import get_plugin_settings
43 from rhodecode.lib.plugins.utils import get_plugin_settings
44 from rhodecode.model.db import User, UserApiKeys
44 from rhodecode.model.db import User, UserApiKeys
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48 DEFAULT_RENDERER = 'jsonrpc_renderer'
48 DEFAULT_RENDERER = 'jsonrpc_renderer'
49 DEFAULT_URL = '/_admin/apiv2'
49 DEFAULT_URL = '/_admin/apiv2'
50
50
51
51
52 def find_methods(jsonrpc_methods, pattern):
52 def find_methods(jsonrpc_methods, pattern):
53 matches = OrderedDict()
53 matches = OrderedDict()
54 if not isinstance(pattern, (list, tuple)):
54 if not isinstance(pattern, (list, tuple)):
55 pattern = [pattern]
55 pattern = [pattern]
56
56
57 for single_pattern in pattern:
57 for single_pattern in pattern:
58 for method_name, method in jsonrpc_methods.items():
58 for method_name, method in jsonrpc_methods.items():
59 if fnmatch.fnmatch(method_name, single_pattern):
59 if fnmatch.fnmatch(method_name, single_pattern):
60 matches[method_name] = method
60 matches[method_name] = method
61 return matches
61 return matches
62
62
63
63
64 class ExtJsonRenderer(object):
64 class ExtJsonRenderer(object):
65 """
65 """
66 Custom renderer that mkaes use of our ext_json lib
66 Custom renderer that mkaes use of our ext_json lib
67
67
68 """
68 """
69
69
70 def __init__(self, serializer=json.dumps, **kw):
70 def __init__(self, serializer=json.dumps, **kw):
71 """ Any keyword arguments will be passed to the ``serializer``
71 """ Any keyword arguments will be passed to the ``serializer``
72 function."""
72 function."""
73 self.serializer = serializer
73 self.serializer = serializer
74 self.kw = kw
74 self.kw = kw
75
75
76 def __call__(self, info):
76 def __call__(self, info):
77 """ Returns a plain JSON-encoded string with content-type
77 """ Returns a plain JSON-encoded string with content-type
78 ``application/json``. The content-type may be overridden by
78 ``application/json``. The content-type may be overridden by
79 setting ``request.response.content_type``."""
79 setting ``request.response.content_type``."""
80
80
81 def _render(value, system):
81 def _render(value, system):
82 request = system.get('request')
82 request = system.get('request')
83 if request is not None:
83 if request is not None:
84 response = request.response
84 response = request.response
85 ct = response.content_type
85 ct = response.content_type
86 if ct == response.default_content_type:
86 if ct == response.default_content_type:
87 response.content_type = 'application/json'
87 response.content_type = 'application/json'
88
88
89 return self.serializer(value, **self.kw)
89 return self.serializer(value, **self.kw)
90
90
91 return _render
91 return _render
92
92
93
93
94 def jsonrpc_response(request, result):
94 def jsonrpc_response(request, result):
95 rpc_id = getattr(request, 'rpc_id', None)
95 rpc_id = getattr(request, 'rpc_id', None)
96 response = request.response
96 response = request.response
97
97
98 # store content_type before render is called
98 # store content_type before render is called
99 ct = response.content_type
99 ct = response.content_type
100
100
101 ret_value = ''
101 ret_value = ''
102 if rpc_id:
102 if rpc_id:
103 ret_value = {
103 ret_value = {
104 'id': rpc_id,
104 'id': rpc_id,
105 'result': result,
105 'result': result,
106 'error': None,
106 'error': None,
107 }
107 }
108
108
109 # fetch deprecation warnings, and store it inside results
109 # fetch deprecation warnings, and store it inside results
110 deprecation = getattr(request, 'rpc_deprecation', None)
110 deprecation = getattr(request, 'rpc_deprecation', None)
111 if deprecation:
111 if deprecation:
112 ret_value['DEPRECATION_WARNING'] = deprecation
112 ret_value['DEPRECATION_WARNING'] = deprecation
113
113
114 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
114 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
115 response.body = safe_str(raw_body, response.charset)
115 response.body = safe_str(raw_body, response.charset)
116
116
117 if ct == response.default_content_type:
117 if ct == response.default_content_type:
118 response.content_type = 'application/json'
118 response.content_type = 'application/json'
119
119
120 return response
120 return response
121
121
122
122
123 def jsonrpc_error(request, message, retid=None, code=None):
123 def jsonrpc_error(request, message, retid=None, code=None):
124 """
124 """
125 Generate a Response object with a JSON-RPC error body
125 Generate a Response object with a JSON-RPC error body
126
126
127 :param code:
127 :param code:
128 :param retid:
128 :param retid:
129 :param message:
129 :param message:
130 """
130 """
131 err_dict = {'id': retid, 'result': None, 'error': message}
131 err_dict = {'id': retid, 'result': None, 'error': message}
132 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
132 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
133 return Response(
133 return Response(
134 body=body,
134 body=body,
135 status=code,
135 status=code,
136 content_type='application/json'
136 content_type='application/json'
137 )
137 )
138
138
139
139
140 def exception_view(exc, request):
140 def exception_view(exc, request):
141 rpc_id = getattr(request, 'rpc_id', None)
141 rpc_id = getattr(request, 'rpc_id', None)
142
142
143 fault_message = 'undefined error'
143 fault_message = 'undefined error'
144 if isinstance(exc, JSONRPCError):
144 if isinstance(exc, JSONRPCError):
145 fault_message = exc.message
145 fault_message = exc.message
146 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
146 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
147 elif isinstance(exc, JSONRPCValidationError):
147 elif isinstance(exc, JSONRPCValidationError):
148 colander_exc = exc.colander_exception
148 colander_exc = exc.colander_exception
149 # TODO(marcink): think maybe of nicer way to serialize errors ?
149 # TODO(marcink): think maybe of nicer way to serialize errors ?
150 fault_message = colander_exc.asdict()
150 fault_message = colander_exc.asdict()
151 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
151 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
152 elif isinstance(exc, JSONRPCForbidden):
152 elif isinstance(exc, JSONRPCForbidden):
153 fault_message = 'Access was denied to this resource.'
153 fault_message = 'Access was denied to this resource.'
154 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
154 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
155 elif isinstance(exc, HTTPNotFound):
155 elif isinstance(exc, HTTPNotFound):
156 method = request.rpc_method
156 method = request.rpc_method
157 log.debug('json-rpc method `%s` not found in list of '
157 log.debug('json-rpc method `%s` not found in list of '
158 'api calls: %s, rpc_id:%s',
158 'api calls: %s, rpc_id:%s',
159 method, request.registry.jsonrpc_methods.keys(), rpc_id)
159 method, request.registry.jsonrpc_methods.keys(), rpc_id)
160
160
161 similar = 'none'
161 similar = 'none'
162 try:
162 try:
163 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
163 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
164 similar_found = find_methods(
164 similar_found = find_methods(
165 request.registry.jsonrpc_methods, similar_paterns)
165 request.registry.jsonrpc_methods, similar_paterns)
166 similar = ', '.join(similar_found.keys()) or similar
166 similar = ', '.join(similar_found.keys()) or similar
167 except Exception:
167 except Exception:
168 # make the whole above block safe
168 # make the whole above block safe
169 pass
169 pass
170
170
171 fault_message = "No such method: {}. Similar methods: {}".format(
171 fault_message = "No such method: {}. Similar methods: {}".format(
172 method, similar)
172 method, similar)
173
173
174 return jsonrpc_error(request, fault_message, rpc_id)
174 return jsonrpc_error(request, fault_message, rpc_id)
175
175
176
176
177 def request_view(request):
177 def request_view(request):
178 """
178 """
179 Main request handling method. It handles all logic to call a specific
179 Main request handling method. It handles all logic to call a specific
180 exposed method
180 exposed method
181 """
181 """
182
182
183 # check if we can find this session using api_key, get_by_auth_token
183 # check if we can find this session using api_key, get_by_auth_token
184 # search not expired tokens only
184 # search not expired tokens only
185
185
186 try:
186 try:
187 api_user = User.get_by_auth_token(request.rpc_api_key)
187 api_user = User.get_by_auth_token(request.rpc_api_key)
188
188
189 if api_user is None:
189 if api_user is None:
190 return jsonrpc_error(
190 return jsonrpc_error(
191 request, retid=request.rpc_id, message='Invalid API KEY')
191 request, retid=request.rpc_id, message='Invalid API KEY')
192
192
193 if not api_user.active:
193 if not api_user.active:
194 return jsonrpc_error(
194 return jsonrpc_error(
195 request, retid=request.rpc_id,
195 request, retid=request.rpc_id,
196 message='Request from this user not allowed')
196 message='Request from this user not allowed')
197
197
198 # check if we are allowed to use this IP
198 # check if we are allowed to use this IP
199 auth_u = AuthUser(
199 auth_u = AuthUser(
200 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
200 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
201 if not auth_u.ip_allowed:
201 if not auth_u.ip_allowed:
202 return jsonrpc_error(
202 return jsonrpc_error(
203 request, retid=request.rpc_id,
203 request, retid=request.rpc_id,
204 message='Request from IP:%s not allowed' % (
204 message='Request from IP:%s not allowed' % (
205 request.rpc_ip_addr,))
205 request.rpc_ip_addr,))
206 else:
206 else:
207 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
207 log.info('Access for IP:%s allowed' % (request.rpc_ip_addr,))
208
208
209 # register our auth-user
209 # register our auth-user
210 request.rpc_user = auth_u
210 request.rpc_user = auth_u
211
211
212 # now check if token is valid for API
212 # now check if token is valid for API
213 auth_token = request.rpc_api_key
213 auth_token = request.rpc_api_key
214 token_match = api_user.authenticate_by_token(
214 token_match = api_user.authenticate_by_token(
215 auth_token, roles=[UserApiKeys.ROLE_API])
215 auth_token, roles=[UserApiKeys.ROLE_API])
216 invalid_token = not token_match
216 invalid_token = not token_match
217
217
218 log.debug('Checking if API KEY is valid with proper role')
218 log.debug('Checking if API KEY is valid with proper role')
219 if invalid_token:
219 if invalid_token:
220 return jsonrpc_error(
220 return jsonrpc_error(
221 request, retid=request.rpc_id,
221 request, retid=request.rpc_id,
222 message='API KEY invalid or, has bad role for an API call')
222 message='API KEY invalid or, has bad role for an API call')
223
223
224 except Exception:
224 except Exception:
225 log.exception('Error on API AUTH')
225 log.exception('Error on API AUTH')
226 return jsonrpc_error(
226 return jsonrpc_error(
227 request, retid=request.rpc_id, message='Invalid API KEY')
227 request, retid=request.rpc_id, message='Invalid API KEY')
228
228
229 method = request.rpc_method
229 method = request.rpc_method
230 func = request.registry.jsonrpc_methods[method]
230 func = request.registry.jsonrpc_methods[method]
231
231
232 # now that we have a method, add request._req_params to
232 # now that we have a method, add request._req_params to
233 # self.kargs and dispatch control to WGIController
233 # self.kargs and dispatch control to WGIController
234 argspec = inspect.getargspec(func)
234 argspec = inspect.getargspec(func)
235 arglist = argspec[0]
235 arglist = argspec[0]
236 defaults = map(type, argspec[3] or [])
236 defaults = map(type, argspec[3] or [])
237 default_empty = types.NotImplementedType
237 default_empty = types.NotImplementedType
238
238
239 # kw arguments required by this method
239 # kw arguments required by this method
240 func_kwargs = dict(itertools.izip_longest(
240 func_kwargs = dict(itertools.izip_longest(
241 reversed(arglist), reversed(defaults), fillvalue=default_empty))
241 reversed(arglist), reversed(defaults), fillvalue=default_empty))
242
242
243 # This attribute will need to be first param of a method that uses
243 # This attribute will need to be first param of a method that uses
244 # api_key, which is translated to instance of user at that name
244 # api_key, which is translated to instance of user at that name
245 user_var = 'apiuser'
245 user_var = 'apiuser'
246 request_var = 'request'
246 request_var = 'request'
247
247
248 for arg in [user_var, request_var]:
248 for arg in [user_var, request_var]:
249 if arg not in arglist:
249 if arg not in arglist:
250 return jsonrpc_error(
250 return jsonrpc_error(
251 request,
251 request,
252 retid=request.rpc_id,
252 retid=request.rpc_id,
253 message='This method [%s] does not support '
253 message='This method [%s] does not support '
254 'required parameter `%s`' % (func.__name__, arg))
254 'required parameter `%s`' % (func.__name__, arg))
255
255
256 # get our arglist and check if we provided them as args
256 # get our arglist and check if we provided them as args
257 for arg, default in func_kwargs.items():
257 for arg, default in func_kwargs.items():
258 if arg in [user_var, request_var]:
258 if arg in [user_var, request_var]:
259 # user_var and request_var are pre-hardcoded parameters and we
259 # user_var and request_var are pre-hardcoded parameters and we
260 # don't need to do any translation
260 # don't need to do any translation
261 continue
261 continue
262
262
263 # skip the required param check if it's default value is
263 # skip the required param check if it's default value is
264 # NotImplementedType (default_empty)
264 # NotImplementedType (default_empty)
265 if default == default_empty and arg not in request.rpc_params:
265 if default == default_empty and arg not in request.rpc_params:
266 return jsonrpc_error(
266 return jsonrpc_error(
267 request,
267 request,
268 retid=request.rpc_id,
268 retid=request.rpc_id,
269 message=('Missing non optional `%s` arg in JSON DATA' % arg)
269 message=('Missing non optional `%s` arg in JSON DATA' % arg)
270 )
270 )
271
271
272 # sanitize extra passed arguments
272 # sanitize extra passed arguments
273 for k in request.rpc_params.keys()[:]:
273 for k in request.rpc_params.keys()[:]:
274 if k not in func_kwargs:
274 if k not in func_kwargs:
275 del request.rpc_params[k]
275 del request.rpc_params[k]
276
276
277 call_params = request.rpc_params
277 call_params = request.rpc_params
278 call_params.update({
278 call_params.update({
279 'request': request,
279 'request': request,
280 'apiuser': auth_u
280 'apiuser': auth_u
281 })
281 })
282
282
283 # register some common functions for usage
283 # register some common functions for usage
284 attach_context_attributes(
284 attach_context_attributes(
285 TemplateArgs(), request, request.rpc_user.user_id)
285 TemplateArgs(), request, request.rpc_user.user_id)
286
286
287 try:
287 try:
288 ret_value = func(**call_params)
288 ret_value = func(**call_params)
289 return jsonrpc_response(request, ret_value)
289 return jsonrpc_response(request, ret_value)
290 except JSONRPCBaseError:
290 except JSONRPCBaseError:
291 raise
291 raise
292 except Exception:
292 except Exception:
293 log.exception('Unhandled exception occurred on api call: %s', func)
293 log.exception('Unhandled exception occurred on api call: %s', func)
294 return jsonrpc_error(request, retid=request.rpc_id,
294 return jsonrpc_error(request, retid=request.rpc_id,
295 message='Internal server error')
295 message='Internal server error')
296
296
297
297
298 def setup_request(request):
298 def setup_request(request):
299 """
299 """
300 Parse a JSON-RPC request body. It's used inside the predicates method
300 Parse a JSON-RPC request body. It's used inside the predicates method
301 to validate and bootstrap requests for usage in rpc calls.
301 to validate and bootstrap requests for usage in rpc calls.
302
302
303 We need to raise JSONRPCError here if we want to return some errors back to
303 We need to raise JSONRPCError here if we want to return some errors back to
304 user.
304 user.
305 """
305 """
306
306
307 log.debug('Executing setup request: %r', request)
307 log.debug('Executing setup request: %r', request)
308 request.rpc_ip_addr = get_ip_addr(request.environ)
308 request.rpc_ip_addr = get_ip_addr(request.environ)
309 # TODO(marcink): deprecate GET at some point
309 # TODO(marcink): deprecate GET at some point
310 if request.method not in ['POST', 'GET']:
310 if request.method not in ['POST', 'GET']:
311 log.debug('unsupported request method "%s"', request.method)
311 log.debug('unsupported request method "%s"', request.method)
312 raise JSONRPCError(
312 raise JSONRPCError(
313 'unsupported request method "%s". Please use POST' % request.method)
313 'unsupported request method "%s". Please use POST' % request.method)
314
314
315 if 'CONTENT_LENGTH' not in request.environ:
315 if 'CONTENT_LENGTH' not in request.environ:
316 log.debug("No Content-Length")
316 log.debug("No Content-Length")
317 raise JSONRPCError("Empty body, No Content-Length in request")
317 raise JSONRPCError("Empty body, No Content-Length in request")
318
318
319 else:
319 else:
320 length = request.environ['CONTENT_LENGTH']
320 length = request.environ['CONTENT_LENGTH']
321 log.debug('Content-Length: %s', length)
321 log.debug('Content-Length: %s', length)
322
322
323 if length == 0:
323 if length == 0:
324 log.debug("Content-Length is 0")
324 log.debug("Content-Length is 0")
325 raise JSONRPCError("Content-Length is 0")
325 raise JSONRPCError("Content-Length is 0")
326
326
327 raw_body = request.body
327 raw_body = request.body
328 try:
328 try:
329 json_body = json.loads(raw_body)
329 json_body = json.loads(raw_body)
330 except ValueError as e:
330 except ValueError as e:
331 # catch JSON errors Here
331 # catch JSON errors Here
332 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
332 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
333
333
334 request.rpc_id = json_body.get('id')
334 request.rpc_id = json_body.get('id')
335 request.rpc_method = json_body.get('method')
335 request.rpc_method = json_body.get('method')
336
336
337 # check required base parameters
337 # check required base parameters
338 try:
338 try:
339 api_key = json_body.get('api_key')
339 api_key = json_body.get('api_key')
340 if not api_key:
340 if not api_key:
341 api_key = json_body.get('auth_token')
341 api_key = json_body.get('auth_token')
342
342
343 if not api_key:
343 if not api_key:
344 raise KeyError('api_key or auth_token')
344 raise KeyError('api_key or auth_token')
345
345
346 # TODO(marcink): support passing in token in request header
346 # TODO(marcink): support passing in token in request header
347
347
348 request.rpc_api_key = api_key
348 request.rpc_api_key = api_key
349 request.rpc_id = json_body['id']
349 request.rpc_id = json_body['id']
350 request.rpc_method = json_body['method']
350 request.rpc_method = json_body['method']
351 request.rpc_params = json_body['args'] \
351 request.rpc_params = json_body['args'] \
352 if isinstance(json_body['args'], dict) else {}
352 if isinstance(json_body['args'], dict) else {}
353
353
354 log.debug(
354 log.debug(
355 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
355 'method: %s, params: %s' % (request.rpc_method, request.rpc_params))
356 except KeyError as e:
356 except KeyError as e:
357 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
357 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
358
358
359 log.debug('setup complete, now handling method:%s rpcid:%s',
359 log.debug('setup complete, now handling method:%s rpcid:%s',
360 request.rpc_method, request.rpc_id, )
360 request.rpc_method, request.rpc_id, )
361
361
362
362
363 class RoutePredicate(object):
363 class RoutePredicate(object):
364 def __init__(self, val, config):
364 def __init__(self, val, config):
365 self.val = val
365 self.val = val
366
366
367 def text(self):
367 def text(self):
368 return 'jsonrpc route = %s' % self.val
368 return 'jsonrpc route = %s' % self.val
369
369
370 phash = text
370 phash = text
371
371
372 def __call__(self, info, request):
372 def __call__(self, info, request):
373 if self.val:
373 if self.val:
374 # potentially setup and bootstrap our call
374 # potentially setup and bootstrap our call
375 setup_request(request)
375 setup_request(request)
376
376
377 # Always return True so that even if it isn't a valid RPC it
377 # Always return True so that even if it isn't a valid RPC it
378 # will fall through to the underlaying handlers like notfound_view
378 # will fall through to the underlaying handlers like notfound_view
379 return True
379 return True
380
380
381
381
382 class NotFoundPredicate(object):
382 class NotFoundPredicate(object):
383 def __init__(self, val, config):
383 def __init__(self, val, config):
384 self.val = val
384 self.val = val
385 self.methods = config.registry.jsonrpc_methods
385 self.methods = config.registry.jsonrpc_methods
386
386
387 def text(self):
387 def text(self):
388 return 'jsonrpc method not found = {}.'.format(self.val)
388 return 'jsonrpc method not found = {}.'.format(self.val)
389
389
390 phash = text
390 phash = text
391
391
392 def __call__(self, info, request):
392 def __call__(self, info, request):
393 return hasattr(request, 'rpc_method')
393 return hasattr(request, 'rpc_method')
394
394
395
395
396 class MethodPredicate(object):
396 class MethodPredicate(object):
397 def __init__(self, val, config):
397 def __init__(self, val, config):
398 self.method = val
398 self.method = val
399
399
400 def text(self):
400 def text(self):
401 return 'jsonrpc method = %s' % self.method
401 return 'jsonrpc method = %s' % self.method
402
402
403 phash = text
403 phash = text
404
404
405 def __call__(self, context, request):
405 def __call__(self, context, request):
406 # we need to explicitly return False here, so pyramid doesn't try to
406 # we need to explicitly return False here, so pyramid doesn't try to
407 # execute our view directly. We need our main handler to execute things
407 # execute our view directly. We need our main handler to execute things
408 return getattr(request, 'rpc_method') == self.method
408 return getattr(request, 'rpc_method') == self.method
409
409
410
410
411 def add_jsonrpc_method(config, view, **kwargs):
411 def add_jsonrpc_method(config, view, **kwargs):
412 # pop the method name
412 # pop the method name
413 method = kwargs.pop('method', None)
413 method = kwargs.pop('method', None)
414
414
415 if method is None:
415 if method is None:
416 raise ConfigurationError(
416 raise ConfigurationError(
417 'Cannot register a JSON-RPC method without specifying the '
417 'Cannot register a JSON-RPC method without specifying the '
418 '"method"')
418 '"method"')
419
419
420 # we define custom predicate, to enable to detect conflicting methods,
420 # we define custom predicate, to enable to detect conflicting methods,
421 # those predicates are kind of "translation" from the decorator variables
421 # those predicates are kind of "translation" from the decorator variables
422 # to internal predicates names
422 # to internal predicates names
423
423
424 kwargs['jsonrpc_method'] = method
424 kwargs['jsonrpc_method'] = method
425
425
426 # register our view into global view store for validation
426 # register our view into global view store for validation
427 config.registry.jsonrpc_methods[method] = view
427 config.registry.jsonrpc_methods[method] = view
428
428
429 # we're using our main request_view handler, here, so each method
429 # we're using our main request_view handler, here, so each method
430 # has a unified handler for itself
430 # has a unified handler for itself
431 config.add_view(request_view, route_name='apiv2', **kwargs)
431 config.add_view(request_view, route_name='apiv2', **kwargs)
432
432
433
433
434 class jsonrpc_method(object):
434 class jsonrpc_method(object):
435 """
435 """
436 decorator that works similar to @add_view_config decorator,
436 decorator that works similar to @add_view_config decorator,
437 but tailored for our JSON RPC
437 but tailored for our JSON RPC
438 """
438 """
439
439
440 venusian = venusian # for testing injection
440 venusian = venusian # for testing injection
441
441
442 def __init__(self, method=None, **kwargs):
442 def __init__(self, method=None, **kwargs):
443 self.method = method
443 self.method = method
444 self.kwargs = kwargs
444 self.kwargs = kwargs
445
445
446 def __call__(self, wrapped):
446 def __call__(self, wrapped):
447 kwargs = self.kwargs.copy()
447 kwargs = self.kwargs.copy()
448 kwargs['method'] = self.method or wrapped.__name__
448 kwargs['method'] = self.method or wrapped.__name__
449 depth = kwargs.pop('_depth', 0)
449 depth = kwargs.pop('_depth', 0)
450
450
451 def callback(context, name, ob):
451 def callback(context, name, ob):
452 config = context.config.with_package(info.module)
452 config = context.config.with_package(info.module)
453 config.add_jsonrpc_method(view=ob, **kwargs)
453 config.add_jsonrpc_method(view=ob, **kwargs)
454
454
455 info = venusian.attach(wrapped, callback, category='pyramid',
455 info = venusian.attach(wrapped, callback, category='pyramid',
456 depth=depth + 1)
456 depth=depth + 1)
457 if info.scope == 'class':
457 if info.scope == 'class':
458 # ensure that attr is set if decorating a class method
458 # ensure that attr is set if decorating a class method
459 kwargs.setdefault('attr', wrapped.__name__)
459 kwargs.setdefault('attr', wrapped.__name__)
460
460
461 kwargs['_info'] = info.codeinfo # fbo action_method
461 kwargs['_info'] = info.codeinfo # fbo action_method
462 return wrapped
462 return wrapped
463
463
464
464
465 class jsonrpc_deprecated_method(object):
465 class jsonrpc_deprecated_method(object):
466 """
466 """
467 Marks method as deprecated, adds log.warning, and inject special key to
467 Marks method as deprecated, adds log.warning, and inject special key to
468 the request variable to mark method as deprecated.
468 the request variable to mark method as deprecated.
469 Also injects special docstring that extract_docs will catch to mark
469 Also injects special docstring that extract_docs will catch to mark
470 method as deprecated.
470 method as deprecated.
471
471
472 :param use_method: specify which method should be used instead of
472 :param use_method: specify which method should be used instead of
473 the decorated one
473 the decorated one
474
474
475 Use like::
475 Use like::
476
476
477 @jsonrpc_method()
477 @jsonrpc_method()
478 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
478 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
479 def old_func(request, apiuser, arg1, arg2):
479 def old_func(request, apiuser, arg1, arg2):
480 ...
480 ...
481 """
481 """
482
482
483 def __init__(self, use_method, deprecated_at_version):
483 def __init__(self, use_method, deprecated_at_version):
484 self.use_method = use_method
484 self.use_method = use_method
485 self.deprecated_at_version = deprecated_at_version
485 self.deprecated_at_version = deprecated_at_version
486 self.deprecated_msg = ''
486 self.deprecated_msg = ''
487
487
488 def __call__(self, func):
488 def __call__(self, func):
489 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
489 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
490 method=self.use_method)
490 method=self.use_method)
491
491
492 docstring = """\n
492 docstring = """\n
493 .. deprecated:: {version}
493 .. deprecated:: {version}
494
494
495 {deprecation_message}
495 {deprecation_message}
496
496
497 {original_docstring}
497 {original_docstring}
498 """
498 """
499 func.__doc__ = docstring.format(
499 func.__doc__ = docstring.format(
500 version=self.deprecated_at_version,
500 version=self.deprecated_at_version,
501 deprecation_message=self.deprecated_msg,
501 deprecation_message=self.deprecated_msg,
502 original_docstring=func.__doc__)
502 original_docstring=func.__doc__)
503 return decorator.decorator(self.__wrapper, func)
503 return decorator.decorator(self.__wrapper, func)
504
504
505 def __wrapper(self, func, *fargs, **fkwargs):
505 def __wrapper(self, func, *fargs, **fkwargs):
506 log.warning('DEPRECATED API CALL on function %s, please '
506 log.warning('DEPRECATED API CALL on function %s, please '
507 'use `%s` instead', func, self.use_method)
507 'use `%s` instead', func, self.use_method)
508 # alter function docstring to mark as deprecated, this is picked up
508 # alter function docstring to mark as deprecated, this is picked up
509 # via fabric file that generates API DOC.
509 # via fabric file that generates API DOC.
510 result = func(*fargs, **fkwargs)
510 result = func(*fargs, **fkwargs)
511
511
512 request = fargs[0]
512 request = fargs[0]
513 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
513 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
514 return result
514 return result
515
515
516
516
517 def includeme(config):
517 def includeme(config):
518 plugin_module = 'rhodecode.api'
518 plugin_module = 'rhodecode.api'
519 plugin_settings = get_plugin_settings(
519 plugin_settings = get_plugin_settings(
520 plugin_module, config.registry.settings)
520 plugin_module, config.registry.settings)
521
521
522 if not hasattr(config.registry, 'jsonrpc_methods'):
522 if not hasattr(config.registry, 'jsonrpc_methods'):
523 config.registry.jsonrpc_methods = OrderedDict()
523 config.registry.jsonrpc_methods = OrderedDict()
524
524
525 # match filter by given method only
525 # match filter by given method only
526 config.add_view_predicate('jsonrpc_method', MethodPredicate)
526 config.add_view_predicate('jsonrpc_method', MethodPredicate)
527
527
528 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
528 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
529 serializer=json.dumps, indent=4))
529 serializer=json.dumps, indent=4))
530 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
530 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
531
531
532 config.add_route_predicate(
532 config.add_route_predicate(
533 'jsonrpc_call', RoutePredicate)
533 'jsonrpc_call', RoutePredicate)
534
534
535 config.add_route(
535 config.add_route(
536 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
536 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
537
537
538 config.scan(plugin_module, ignore='rhodecode.api.tests')
538 config.scan(plugin_module, ignore='rhodecode.api.tests')
539 # register some exception handling view
539 # register some exception handling view
540 config.add_view(exception_view, context=JSONRPCBaseError)
540 config.add_view(exception_view, context=JSONRPCBaseError)
541 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
541 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
542 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
542 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,39 +1,39 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 class JSONRPCBaseError(Exception):
22 class JSONRPCBaseError(Exception):
23 pass
23 pass
24
24
25
25
26 class JSONRPCError(JSONRPCBaseError):
26 class JSONRPCError(JSONRPCBaseError):
27 pass
27 pass
28
28
29
29
30 class JSONRPCValidationError(JSONRPCBaseError):
30 class JSONRPCValidationError(JSONRPCBaseError):
31
31
32 def __init__(self, *args, **kwargs):
32 def __init__(self, *args, **kwargs):
33 self.colander_exception = kwargs.pop('colander_exc')
33 self.colander_exception = kwargs.pop('colander_exc')
34 super(JSONRPCValidationError, self).__init__(*args, **kwargs)
34 super(JSONRPCValidationError, self).__init__(*args, **kwargs)
35
35
36
36
37 class JSONRPCForbidden(JSONRPCBaseError):
37 class JSONRPCForbidden(JSONRPCBaseError):
38 pass
38 pass
39
39
@@ -1,19 +1,19 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,52 +1,52 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.auth_token import AuthTokenModel
25 from rhodecode.model.auth_token import AuthTokenModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27
27
28
28
29 @pytest.fixture(scope="class")
29 @pytest.fixture(scope="class")
30 def testuser_api(request, baseapp):
30 def testuser_api(request, baseapp):
31 cls = request.cls
31 cls = request.cls
32
32
33 # ADMIN USER
33 # ADMIN USER
34 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
34 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
35 cls.apikey = cls.usr.api_key
35 cls.apikey = cls.usr.api_key
36
36
37 # REGULAR USER
37 # REGULAR USER
38 cls.test_user = UserModel().create_or_update(
38 cls.test_user = UserModel().create_or_update(
39 username='test-api',
39 username='test-api',
40 password='test',
40 password='test',
41 email='test@api.rhodecode.org',
41 email='test@api.rhodecode.org',
42 firstname='first',
42 firstname='first',
43 lastname='last'
43 lastname='last'
44 )
44 )
45 # create TOKEN for user, if he doesn't have one
45 # create TOKEN for user, if he doesn't have one
46 if not cls.test_user.api_key:
46 if not cls.test_user.api_key:
47 AuthTokenModel().create(
47 AuthTokenModel().create(
48 user=cls.test_user, description=u'TEST_USER_TOKEN')
48 user=cls.test_user, description=u'TEST_USER_TOKEN')
49
49
50 Session().commit()
50 Session().commit()
51 cls.TEST_USER_LOGIN = cls.test_user.username
51 cls.TEST_USER_LOGIN = cls.test_user.username
52 cls.apikey_regular = cls.test_user.api_key
52 cls.apikey_regular = cls.test_user.api_key
@@ -1,62 +1,62 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import Repository, RepositoryField
23 from rhodecode.model.db import Repository, RepositoryField
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error)
25 build_data, api_call, assert_ok, assert_error)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestAddFieldToRepo(object):
29 class TestAddFieldToRepo(object):
30 def test_api_add_field_to_repo(self, backend):
30 def test_api_add_field_to_repo(self, backend):
31 repo = backend.create_repo()
31 repo = backend.create_repo()
32 repo_name = repo.repo_name
32 repo_name = repo.repo_name
33 id_, params = build_data(
33 id_, params = build_data(
34 self.apikey, 'add_field_to_repo',
34 self.apikey, 'add_field_to_repo',
35 repoid=repo_name,
35 repoid=repo_name,
36 key='extra_field',
36 key='extra_field',
37 label='extra_field_label',
37 label='extra_field_label',
38 description='extra_field_desc')
38 description='extra_field_desc')
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40 expected = {
40 expected = {
41 'msg': 'Added new repository field `extra_field`',
41 'msg': 'Added new repository field `extra_field`',
42 'success': True,
42 'success': True,
43 }
43 }
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 repo = Repository.get_by_repo_name(repo_name)
46 repo = Repository.get_by_repo_name(repo_name)
47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
48 _data = repo_field.get_dict()
48 _data = repo_field.get_dict()
49 assert _data['field_desc'] == 'extra_field_desc'
49 assert _data['field_desc'] == 'extra_field_desc'
50 assert _data['field_key'] == 'extra_field'
50 assert _data['field_key'] == 'extra_field'
51 assert _data['field_label'] == 'extra_field_label'
51 assert _data['field_label'] == 'extra_field_label'
52
52
53 id_, params = build_data(
53 id_, params = build_data(
54 self.apikey, 'add_field_to_repo',
54 self.apikey, 'add_field_to_repo',
55 repoid=repo_name,
55 repoid=repo_name,
56 key='extra_field',
56 key='extra_field',
57 label='extra_field_label',
57 label='extra_field_label',
58 description='extra_field_desc')
58 description='extra_field_desc')
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60 expected = 'Field with key `extra_field` exists for repo `%s`' % (
60 expected = 'Field with key `extra_field` exists for repo `%s`' % (
61 repo_name)
61 repo_name)
62 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
@@ -1,72 +1,72 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestAddUserToUserGroup(object):
30 class TestAddUserToUserGroup(object):
31 def test_api_add_user_to_user_group(self, user_util):
31 def test_api_add_user_to_user_group(self, user_util):
32 group = user_util.create_user_group()
32 group = user_util.create_user_group()
33 user = user_util.create_user()
33 user = user_util.create_user()
34 group_name = group.users_group_name
34 group_name = group.users_group_name
35 user_name = user.username
35 user_name = user.username
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'add_user_to_user_group',
37 self.apikey, 'add_user_to_user_group',
38 usergroupid=group_name, userid=user_name)
38 usergroupid=group_name, userid=user_name)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40 expected = {
40 expected = {
41 'msg': 'added member `%s` to user group `%s`' % (
41 'msg': 'added member `%s` to user group `%s`' % (
42 user_name, group_name
42 user_name, group_name
43 ),
43 ),
44 'success': True
44 'success': True
45 }
45 }
46 assert_ok(id_, expected, given=response.body)
46 assert_ok(id_, expected, given=response.body)
47
47
48 def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util):
48 def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util):
49 user = user_util.create_user()
49 user = user_util.create_user()
50 user_name = user.username
50 user_name = user.username
51 id_, params = build_data(
51 id_, params = build_data(
52 self.apikey, 'add_user_to_user_group',
52 self.apikey, 'add_user_to_user_group',
53 usergroupid='false-group',
53 usergroupid='false-group',
54 userid=user_name)
54 userid=user_name)
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56
56
57 expected = 'user group `%s` does not exist' % 'false-group'
57 expected = 'user group `%s` does not exist' % 'false-group'
58 assert_error(id_, expected, given=response.body)
58 assert_error(id_, expected, given=response.body)
59
59
60 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
60 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
61 def test_api_add_user_to_user_group_exception_occurred(self, user_util):
61 def test_api_add_user_to_user_group_exception_occurred(self, user_util):
62 group = user_util.create_user_group()
62 group = user_util.create_user_group()
63 user = user_util.create_user()
63 user = user_util.create_user()
64 group_name = group.users_group_name
64 group_name = group.users_group_name
65 user_name = user.username
65 user_name = user.username
66 id_, params = build_data(
66 id_, params = build_data(
67 self.apikey, 'add_user_to_user_group',
67 self.apikey, 'add_user_to_user_group',
68 usergroupid=group_name, userid=user_name)
68 usergroupid=group_name, userid=user_name)
69 response = api_call(self.app, params)
69 response = api_call(self.app, params)
70
70
71 expected = 'failed to add member to user group `%s`' % (group_name,)
71 expected = 'failed to add member to user group `%s`' % (group_name,)
72 assert_error(id_, expected, given=response.body)
72 assert_error(id_, expected, given=response.body)
@@ -1,131 +1,131 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.utils import Optional, OAttr
23 from rhodecode.api.utils import Optional, OAttr
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok)
25 build_data, api_call, assert_error, assert_ok)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApi(object):
29 class TestApi(object):
30 maxDiff = None
30 maxDiff = None
31
31
32 def test_Optional_object(self):
32 def test_Optional_object(self):
33
33
34 option1 = Optional(None)
34 option1 = Optional(None)
35 assert '<Optional:%s>' % (None,) == repr(option1)
35 assert '<Optional:%s>' % (None,) == repr(option1)
36 assert option1() is None
36 assert option1() is None
37
37
38 assert 1 == Optional.extract(Optional(1))
38 assert 1 == Optional.extract(Optional(1))
39 assert 'example' == Optional.extract('example')
39 assert 'example' == Optional.extract('example')
40
40
41 def test_Optional_OAttr(self):
41 def test_Optional_OAttr(self):
42 option1 = Optional(OAttr('apiuser'))
42 option1 = Optional(OAttr('apiuser'))
43 assert 'apiuser' == Optional.extract(option1)
43 assert 'apiuser' == Optional.extract(option1)
44
44
45 def test_OAttr_object(self):
45 def test_OAttr_object(self):
46 oattr1 = OAttr('apiuser')
46 oattr1 = OAttr('apiuser')
47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
48 assert oattr1() == oattr1
48 assert oattr1() == oattr1
49
49
50 def test_api_wrong_key(self):
50 def test_api_wrong_key(self):
51 id_, params = build_data('trololo', 'get_user')
51 id_, params = build_data('trololo', 'get_user')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 expected = 'Invalid API KEY'
54 expected = 'Invalid API KEY'
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
56
56
57 def test_api_missing_non_optional_param(self):
57 def test_api_missing_non_optional_param(self):
58 id_, params = build_data(self.apikey, 'get_repo')
58 id_, params = build_data(self.apikey, 'get_repo')
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 expected = 'Missing non optional `repoid` arg in JSON DATA'
61 expected = 'Missing non optional `repoid` arg in JSON DATA'
62 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
63
63
64 def test_api_missing_non_optional_param_args_null(self):
64 def test_api_missing_non_optional_param_args_null(self):
65 id_, params = build_data(self.apikey, 'get_repo')
65 id_, params = build_data(self.apikey, 'get_repo')
66 params = params.replace('"args": {}', '"args": null')
66 params = params.replace('"args": {}', '"args": null')
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 expected = 'Missing non optional `repoid` arg in JSON DATA'
69 expected = 'Missing non optional `repoid` arg in JSON DATA'
70 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
71
71
72 def test_api_missing_non_optional_param_args_bad(self):
72 def test_api_missing_non_optional_param_args_bad(self):
73 id_, params = build_data(self.apikey, 'get_repo')
73 id_, params = build_data(self.apikey, 'get_repo')
74 params = params.replace('"args": {}', '"args": 1')
74 params = params.replace('"args": {}', '"args": 1')
75 response = api_call(self.app, params)
75 response = api_call(self.app, params)
76
76
77 expected = 'Missing non optional `repoid` arg in JSON DATA'
77 expected = 'Missing non optional `repoid` arg in JSON DATA'
78 assert_error(id_, expected, given=response.body)
78 assert_error(id_, expected, given=response.body)
79
79
80 def test_api_non_existing_method(self, request):
80 def test_api_non_existing_method(self, request):
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = 'No such method: not_existing. Similar methods: none'
83 expected = 'No such method: not_existing. Similar methods: none'
84 assert_error(id_, expected, given=response.body)
84 assert_error(id_, expected, given=response.body)
85
85
86 def test_api_non_existing_method_have_similar(self, request):
86 def test_api_non_existing_method_have_similar(self, request):
87 id_, params = build_data(self.apikey, 'comment', args='xx')
87 id_, params = build_data(self.apikey, 'comment', args='xx')
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89 expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, get_pull_request_comments, comment_commit'
89 expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, get_pull_request_comments, comment_commit'
90 assert_error(id_, expected, given=response.body)
90 assert_error(id_, expected, given=response.body)
91
91
92 def test_api_disabled_user(self, request):
92 def test_api_disabled_user(self, request):
93
93
94 def set_active(active):
94 def set_active(active):
95 from rhodecode.model.db import Session, User
95 from rhodecode.model.db import Session, User
96 user = User.get_by_auth_token(self.apikey)
96 user = User.get_by_auth_token(self.apikey)
97 user.active = active
97 user.active = active
98 Session().add(user)
98 Session().add(user)
99 Session().commit()
99 Session().commit()
100
100
101 request.addfinalizer(lambda: set_active(True))
101 request.addfinalizer(lambda: set_active(True))
102
102
103 set_active(False)
103 set_active(False)
104 id_, params = build_data(self.apikey, 'test', args='xx')
104 id_, params = build_data(self.apikey, 'test', args='xx')
105 response = api_call(self.app, params)
105 response = api_call(self.app, params)
106 expected = 'Request from this user not allowed'
106 expected = 'Request from this user not allowed'
107 assert_error(id_, expected, given=response.body)
107 assert_error(id_, expected, given=response.body)
108
108
109 def test_api_args_is_null(self):
109 def test_api_args_is_null(self):
110 __, params = build_data(self.apikey, 'get_users', )
110 __, params = build_data(self.apikey, 'get_users', )
111 params = params.replace('"args": {}', '"args": null')
111 params = params.replace('"args": {}', '"args": null')
112 response = api_call(self.app, params)
112 response = api_call(self.app, params)
113 assert response.status == '200 OK'
113 assert response.status == '200 OK'
114
114
115 def test_api_args_is_bad(self):
115 def test_api_args_is_bad(self):
116 __, params = build_data(self.apikey, 'get_users', )
116 __, params = build_data(self.apikey, 'get_users', )
117 params = params.replace('"args": {}', '"args": 1')
117 params = params.replace('"args": {}', '"args": 1')
118 response = api_call(self.app, params)
118 response = api_call(self.app, params)
119 assert response.status == '200 OK'
119 assert response.status == '200 OK'
120
120
121 def test_api_args_different_args(self):
121 def test_api_args_different_args(self):
122 import string
122 import string
123 expected = {
123 expected = {
124 'ascii_letters': string.ascii_letters,
124 'ascii_letters': string.ascii_letters,
125 'ws': string.whitespace,
125 'ws': string.whitespace,
126 'printables': string.printable
126 'printables': string.printable
127 }
127 }
128 id_, params = build_data(self.apikey, 'test', args=expected)
128 id_, params = build_data(self.apikey, 'test', args=expected)
129 response = api_call(self.app, params)
129 response = api_call(self.app, params)
130 assert response.status == '200 OK'
130 assert response.status == '200 OK'
131 assert_ok(id_, expected, response.body)
131 assert_ok(id_, expected, response.body)
@@ -1,44 +1,44 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.user_sessions import FileAuthSessions
24 from rhodecode.lib.user_sessions import FileAuthSessions
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, crash)
26 build_data, api_call, assert_ok, assert_error, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestCleanupSessions(object):
30 class TestCleanupSessions(object):
31 def test_api_cleanup_sessions(self):
31 def test_api_cleanup_sessions(self):
32 id_, params = build_data(self.apikey, 'cleanup_sessions')
32 id_, params = build_data(self.apikey, 'cleanup_sessions')
33 response = api_call(self.app, params)
33 response = api_call(self.app, params)
34
34
35 expected = {'backend': 'file sessions', 'sessions_removed': 0}
35 expected = {'backend': 'file sessions', 'sessions_removed': 0}
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
37
37
38 @mock.patch.object(FileAuthSessions, 'clean_sessions', crash)
38 @mock.patch.object(FileAuthSessions, 'clean_sessions', crash)
39 def test_api_cleanup_error(self):
39 def test_api_cleanup_error(self):
40 id_, params = build_data(self.apikey, 'cleanup_sessions', )
40 id_, params = build_data(self.apikey, 'cleanup_sessions', )
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = 'Error occurred during session cleanup'
43 expected = 'Error occurred during session cleanup'
44 assert_error(id_, expected, given=response.body)
44 assert_error(id_, expected, given=response.body)
@@ -1,113 +1,113 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserLog
23 from rhodecode.model.db import UserLog
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestClosePullRequest(object):
31 class TestClosePullRequest(object):
32
32
33 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
34 def test_api_close_pull_request(self, pr_util):
34 def test_api_close_pull_request(self, pr_util):
35 pull_request = pr_util.create_pull_request()
35 pull_request = pr_util.create_pull_request()
36 pull_request_id = pull_request.pull_request_id
36 pull_request_id = pull_request.pull_request_id
37 author = pull_request.user_id
37 author = pull_request.user_id
38 repo = pull_request.target_repo.repo_id
38 repo = pull_request.target_repo.repo_id
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'close_pull_request',
40 self.apikey, 'close_pull_request',
41 repoid=pull_request.target_repo.repo_name,
41 repoid=pull_request.target_repo.repo_name,
42 pullrequestid=pull_request.pull_request_id)
42 pullrequestid=pull_request.pull_request_id)
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44 expected = {
44 expected = {
45 'pull_request_id': pull_request_id,
45 'pull_request_id': pull_request_id,
46 'close_status': 'Rejected',
46 'close_status': 'Rejected',
47 'closed': True,
47 'closed': True,
48 }
48 }
49 assert_ok(id_, expected, response.body)
49 assert_ok(id_, expected, response.body)
50 journal = UserLog.query()\
50 journal = UserLog.query()\
51 .filter(UserLog.user_id == author) \
51 .filter(UserLog.user_id == author) \
52 .order_by('user_log_id') \
52 .order_by('user_log_id') \
53 .filter(UserLog.repository_id == repo)\
53 .filter(UserLog.repository_id == repo)\
54 .all()
54 .all()
55 assert journal[-1].action == 'repo.pull_request.close'
55 assert journal[-1].action == 'repo.pull_request.close'
56
56
57 @pytest.mark.backends("git", "hg")
57 @pytest.mark.backends("git", "hg")
58 def test_api_close_pull_request_already_closed_error(self, pr_util):
58 def test_api_close_pull_request_already_closed_error(self, pr_util):
59 pull_request = pr_util.create_pull_request()
59 pull_request = pr_util.create_pull_request()
60 pull_request_id = pull_request.pull_request_id
60 pull_request_id = pull_request.pull_request_id
61 pull_request_repo = pull_request.target_repo.repo_name
61 pull_request_repo = pull_request.target_repo.repo_name
62 PullRequestModel().close_pull_request(
62 PullRequestModel().close_pull_request(
63 pull_request, pull_request.author)
63 pull_request, pull_request.author)
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'close_pull_request',
65 self.apikey, 'close_pull_request',
66 repoid=pull_request_repo, pullrequestid=pull_request_id)
66 repoid=pull_request_repo, pullrequestid=pull_request_id)
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 expected = 'pull request `%s` is already closed' % pull_request_id
69 expected = 'pull request `%s` is already closed' % pull_request_id
70 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
71
71
72 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
73 def test_api_close_pull_request_repo_error(self, pr_util):
73 def test_api_close_pull_request_repo_error(self, pr_util):
74 pull_request = pr_util.create_pull_request()
74 pull_request = pr_util.create_pull_request()
75 id_, params = build_data(
75 id_, params = build_data(
76 self.apikey, 'close_pull_request',
76 self.apikey, 'close_pull_request',
77 repoid=666, pullrequestid=pull_request.pull_request_id)
77 repoid=666, pullrequestid=pull_request.pull_request_id)
78 response = api_call(self.app, params)
78 response = api_call(self.app, params)
79
79
80 expected = 'repository `666` does not exist'
80 expected = 'repository `666` does not exist'
81 assert_error(id_, expected, given=response.body)
81 assert_error(id_, expected, given=response.body)
82
82
83 @pytest.mark.backends("git", "hg")
83 @pytest.mark.backends("git", "hg")
84 def test_api_close_pull_request_non_admin_with_userid_error(self,
84 def test_api_close_pull_request_non_admin_with_userid_error(self,
85 pr_util):
85 pr_util):
86 pull_request = pr_util.create_pull_request()
86 pull_request = pr_util.create_pull_request()
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'close_pull_request',
88 self.apikey_regular, 'close_pull_request',
89 repoid=pull_request.target_repo.repo_name,
89 repoid=pull_request.target_repo.repo_name,
90 pullrequestid=pull_request.pull_request_id,
90 pullrequestid=pull_request.pull_request_id,
91 userid=TEST_USER_ADMIN_LOGIN)
91 userid=TEST_USER_ADMIN_LOGIN)
92 response = api_call(self.app, params)
92 response = api_call(self.app, params)
93
93
94 expected = 'userid is not the same as your user'
94 expected = 'userid is not the same as your user'
95 assert_error(id_, expected, given=response.body)
95 assert_error(id_, expected, given=response.body)
96
96
97 @pytest.mark.backends("git", "hg")
97 @pytest.mark.backends("git", "hg")
98 def test_api_close_pull_request_no_perms_to_close(
98 def test_api_close_pull_request_no_perms_to_close(
99 self, user_util, pr_util):
99 self, user_util, pr_util):
100 user = user_util.create_user()
100 user = user_util.create_user()
101 pull_request = pr_util.create_pull_request()
101 pull_request = pr_util.create_pull_request()
102
102
103 id_, params = build_data(
103 id_, params = build_data(
104 user.api_key, 'close_pull_request',
104 user.api_key, 'close_pull_request',
105 repoid=pull_request.target_repo.repo_name,
105 repoid=pull_request.target_repo.repo_name,
106 pullrequestid=pull_request.pull_request_id,)
106 pullrequestid=pull_request.pull_request_id,)
107 response = api_call(self.app, params)
107 response = api_call(self.app, params)
108
108
109 expected = ('pull request `%s` close failed, '
109 expected = ('pull request `%s` close failed, '
110 'no permission to close.') % pull_request.pull_request_id
110 'no permission to close.') % pull_request.pull_request_id
111
111
112 response_json = response.json['error']
112 response_json = response.json['error']
113 assert response_json == expected
113 assert response_json == expected
@@ -1,81 +1,81 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import ChangesetStatus
23 from rhodecode.model.db import ChangesetStatus
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok)
25 build_data, api_call, assert_error, assert_ok)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestCommentCommit(object):
29 class TestCommentCommit(object):
30 def test_api_comment_commit_on_empty_repo(self, backend):
30 def test_api_comment_commit_on_empty_repo(self, backend):
31 repo = backend.create_repo()
31 repo = backend.create_repo()
32 id_, params = build_data(
32 id_, params = build_data(
33 self.apikey, 'comment_commit', repoid=repo.repo_name,
33 self.apikey, 'comment_commit', repoid=repo.repo_name,
34 commit_id='tip', message='message', status_change=None)
34 commit_id='tip', message='message', status_change=None)
35 response = api_call(self.app, params)
35 response = api_call(self.app, params)
36 expected = 'There are no commits yet'
36 expected = 'There are no commits yet'
37 assert_error(id_, expected, given=response.body)
37 assert_error(id_, expected, given=response.body)
38
38
39 @pytest.mark.parametrize("commit_id, expected_err", [
39 @pytest.mark.parametrize("commit_id, expected_err", [
40 ('abcabca', {'hg': 'Commit {commit} does not exist for {repo}',
40 ('abcabca', {'hg': 'Commit {commit} does not exist for {repo}',
41 'git': 'Commit {commit} does not exist for {repo}',
41 'git': 'Commit {commit} does not exist for {repo}',
42 'svn': 'Commit id {commit} not understood.'}),
42 'svn': 'Commit id {commit} not understood.'}),
43 ('idontexist', {'hg': 'Commit {commit} does not exist for {repo}',
43 ('idontexist', {'hg': 'Commit {commit} does not exist for {repo}',
44 'git': 'Commit {commit} does not exist for {repo}',
44 'git': 'Commit {commit} does not exist for {repo}',
45 'svn': 'Commit id {commit} not understood.'}),
45 'svn': 'Commit id {commit} not understood.'}),
46 ])
46 ])
47 def test_api_comment_commit_wrong_hash(self, backend, commit_id, expected_err):
47 def test_api_comment_commit_wrong_hash(self, backend, commit_id, expected_err):
48 repo_name = backend.repo.repo_name
48 repo_name = backend.repo.repo_name
49 id_, params = build_data(
49 id_, params = build_data(
50 self.apikey, 'comment_commit', repoid=repo_name,
50 self.apikey, 'comment_commit', repoid=repo_name,
51 commit_id=commit_id, message='message', status_change=None)
51 commit_id=commit_id, message='message', status_change=None)
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 expected_err = expected_err[backend.alias]
54 expected_err = expected_err[backend.alias]
55 expected_err = expected_err.format(
55 expected_err = expected_err.format(
56 repo=backend.repo.scm_instance(), commit=commit_id)
56 repo=backend.repo.scm_instance(), commit=commit_id)
57 assert_error(id_, expected_err, given=response.body)
57 assert_error(id_, expected_err, given=response.body)
58
58
59 @pytest.mark.parametrize("status_change, message, commit_id", [
59 @pytest.mark.parametrize("status_change, message, commit_id", [
60 (None, 'Hallo', 'tip'),
60 (None, 'Hallo', 'tip'),
61 (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'),
61 (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'),
62 (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'),
62 (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'),
63 ])
63 ])
64 def test_api_comment_commit(
64 def test_api_comment_commit(
65 self, backend, status_change, message, commit_id,
65 self, backend, status_change, message, commit_id,
66 no_notifications):
66 no_notifications):
67
67
68 commit_id = backend.repo.scm_instance().get_changeset(commit_id).raw_id
68 commit_id = backend.repo.scm_instance().get_changeset(commit_id).raw_id
69
69
70 id_, params = build_data(
70 id_, params = build_data(
71 self.apikey, 'comment_commit', repoid=backend.repo_name,
71 self.apikey, 'comment_commit', repoid=backend.repo_name,
72 commit_id=commit_id, message=message, status=status_change)
72 commit_id=commit_id, message=message, status=status_change)
73 response = api_call(self.app, params)
73 response = api_call(self.app, params)
74 repo = backend.repo.scm_instance()
74 repo = backend.repo.scm_instance()
75 expected = {
75 expected = {
76 'msg': 'Commented on commit `%s` for repository `%s`' % (
76 'msg': 'Commented on commit `%s` for repository `%s`' % (
77 repo.get_changeset().raw_id, backend.repo_name),
77 repo.get_changeset().raw_id, backend.repo_name),
78 'status_change': status_change,
78 'status_change': status_change,
79 'success': True
79 'success': True
80 }
80 }
81 assert_ok(id_, expected, given=response.body)
81 assert_ok(id_, expected, given=response.body)
@@ -1,209 +1,209 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.comment import CommentsModel
23 from rhodecode.model.comment import CommentsModel
24 from rhodecode.model.db import UserLog
24 from rhodecode.model.db import UserLog
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
28 build_data, api_call, assert_error, assert_ok)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestCommentPullRequest(object):
32 class TestCommentPullRequest(object):
33 finalizers = []
33 finalizers = []
34
34
35 def teardown_method(self, method):
35 def teardown_method(self, method):
36 if self.finalizers:
36 if self.finalizers:
37 for finalizer in self.finalizers:
37 for finalizer in self.finalizers:
38 finalizer()
38 finalizer()
39 self.finalizers = []
39 self.finalizers = []
40
40
41 @pytest.mark.backends("git", "hg")
41 @pytest.mark.backends("git", "hg")
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
42 def test_api_comment_pull_request(self, pr_util, no_notifications):
43 pull_request = pr_util.create_pull_request()
43 pull_request = pr_util.create_pull_request()
44 pull_request_id = pull_request.pull_request_id
44 pull_request_id = pull_request.pull_request_id
45 author = pull_request.user_id
45 author = pull_request.user_id
46 repo = pull_request.target_repo.repo_id
46 repo = pull_request.target_repo.repo_id
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey, 'comment_pull_request',
48 self.apikey, 'comment_pull_request',
49 repoid=pull_request.target_repo.repo_name,
49 repoid=pull_request.target_repo.repo_name,
50 pullrequestid=pull_request.pull_request_id,
50 pullrequestid=pull_request.pull_request_id,
51 message='test message')
51 message='test message')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
54
54
55 comments = CommentsModel().get_comments(
55 comments = CommentsModel().get_comments(
56 pull_request.target_repo.repo_id, pull_request=pull_request)
56 pull_request.target_repo.repo_id, pull_request=pull_request)
57
57
58 expected = {
58 expected = {
59 'pull_request_id': pull_request.pull_request_id,
59 'pull_request_id': pull_request.pull_request_id,
60 'comment_id': comments[-1].comment_id,
60 'comment_id': comments[-1].comment_id,
61 'status': {'given': None, 'was_changed': None}
61 'status': {'given': None, 'was_changed': None}
62 }
62 }
63 assert_ok(id_, expected, response.body)
63 assert_ok(id_, expected, response.body)
64
64
65 journal = UserLog.query()\
65 journal = UserLog.query()\
66 .filter(UserLog.user_id == author)\
66 .filter(UserLog.user_id == author)\
67 .filter(UserLog.repository_id == repo) \
67 .filter(UserLog.repository_id == repo) \
68 .order_by('user_log_id') \
68 .order_by('user_log_id') \
69 .all()
69 .all()
70 assert journal[-1].action == 'repo.pull_request.comment.create'
70 assert journal[-1].action == 'repo.pull_request.comment.create'
71
71
72 @pytest.mark.backends("git", "hg")
72 @pytest.mark.backends("git", "hg")
73 def test_api_comment_pull_request_change_status(
73 def test_api_comment_pull_request_change_status(
74 self, pr_util, no_notifications):
74 self, pr_util, no_notifications):
75 pull_request = pr_util.create_pull_request()
75 pull_request = pr_util.create_pull_request()
76 pull_request_id = pull_request.pull_request_id
76 pull_request_id = pull_request.pull_request_id
77 id_, params = build_data(
77 id_, params = build_data(
78 self.apikey, 'comment_pull_request',
78 self.apikey, 'comment_pull_request',
79 repoid=pull_request.target_repo.repo_name,
79 repoid=pull_request.target_repo.repo_name,
80 pullrequestid=pull_request.pull_request_id,
80 pullrequestid=pull_request.pull_request_id,
81 status='rejected')
81 status='rejected')
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 pull_request = PullRequestModel().get(pull_request_id)
83 pull_request = PullRequestModel().get(pull_request_id)
84
84
85 comments = CommentsModel().get_comments(
85 comments = CommentsModel().get_comments(
86 pull_request.target_repo.repo_id, pull_request=pull_request)
86 pull_request.target_repo.repo_id, pull_request=pull_request)
87 expected = {
87 expected = {
88 'pull_request_id': pull_request.pull_request_id,
88 'pull_request_id': pull_request.pull_request_id,
89 'comment_id': comments[-1].comment_id,
89 'comment_id': comments[-1].comment_id,
90 'status': {'given': 'rejected', 'was_changed': True}
90 'status': {'given': 'rejected', 'was_changed': True}
91 }
91 }
92 assert_ok(id_, expected, response.body)
92 assert_ok(id_, expected, response.body)
93
93
94 @pytest.mark.backends("git", "hg")
94 @pytest.mark.backends("git", "hg")
95 def test_api_comment_pull_request_change_status_with_specific_commit_id(
95 def test_api_comment_pull_request_change_status_with_specific_commit_id(
96 self, pr_util, no_notifications):
96 self, pr_util, no_notifications):
97 pull_request = pr_util.create_pull_request()
97 pull_request = pr_util.create_pull_request()
98 pull_request_id = pull_request.pull_request_id
98 pull_request_id = pull_request.pull_request_id
99 latest_commit_id = 'test_commit'
99 latest_commit_id = 'test_commit'
100 # inject additional revision, to fail test the status change on
100 # inject additional revision, to fail test the status change on
101 # non-latest commit
101 # non-latest commit
102 pull_request.revisions = pull_request.revisions + ['test_commit']
102 pull_request.revisions = pull_request.revisions + ['test_commit']
103
103
104 id_, params = build_data(
104 id_, params = build_data(
105 self.apikey, 'comment_pull_request',
105 self.apikey, 'comment_pull_request',
106 repoid=pull_request.target_repo.repo_name,
106 repoid=pull_request.target_repo.repo_name,
107 pullrequestid=pull_request.pull_request_id,
107 pullrequestid=pull_request.pull_request_id,
108 status='approved', commit_id=latest_commit_id)
108 status='approved', commit_id=latest_commit_id)
109 response = api_call(self.app, params)
109 response = api_call(self.app, params)
110 pull_request = PullRequestModel().get(pull_request_id)
110 pull_request = PullRequestModel().get(pull_request_id)
111
111
112 expected = {
112 expected = {
113 'pull_request_id': pull_request.pull_request_id,
113 'pull_request_id': pull_request.pull_request_id,
114 'comment_id': None,
114 'comment_id': None,
115 'status': {'given': 'approved', 'was_changed': False}
115 'status': {'given': 'approved', 'was_changed': False}
116 }
116 }
117 assert_ok(id_, expected, response.body)
117 assert_ok(id_, expected, response.body)
118
118
119 @pytest.mark.backends("git", "hg")
119 @pytest.mark.backends("git", "hg")
120 def test_api_comment_pull_request_change_status_with_specific_commit_id(
120 def test_api_comment_pull_request_change_status_with_specific_commit_id(
121 self, pr_util, no_notifications):
121 self, pr_util, no_notifications):
122 pull_request = pr_util.create_pull_request()
122 pull_request = pr_util.create_pull_request()
123 pull_request_id = pull_request.pull_request_id
123 pull_request_id = pull_request.pull_request_id
124 latest_commit_id = pull_request.revisions[0]
124 latest_commit_id = pull_request.revisions[0]
125
125
126 id_, params = build_data(
126 id_, params = build_data(
127 self.apikey, 'comment_pull_request',
127 self.apikey, 'comment_pull_request',
128 repoid=pull_request.target_repo.repo_name,
128 repoid=pull_request.target_repo.repo_name,
129 pullrequestid=pull_request.pull_request_id,
129 pullrequestid=pull_request.pull_request_id,
130 status='approved', commit_id=latest_commit_id)
130 status='approved', commit_id=latest_commit_id)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132 pull_request = PullRequestModel().get(pull_request_id)
132 pull_request = PullRequestModel().get(pull_request_id)
133
133
134 comments = CommentsModel().get_comments(
134 comments = CommentsModel().get_comments(
135 pull_request.target_repo.repo_id, pull_request=pull_request)
135 pull_request.target_repo.repo_id, pull_request=pull_request)
136 expected = {
136 expected = {
137 'pull_request_id': pull_request.pull_request_id,
137 'pull_request_id': pull_request.pull_request_id,
138 'comment_id': comments[-1].comment_id,
138 'comment_id': comments[-1].comment_id,
139 'status': {'given': 'approved', 'was_changed': True}
139 'status': {'given': 'approved', 'was_changed': True}
140 }
140 }
141 assert_ok(id_, expected, response.body)
141 assert_ok(id_, expected, response.body)
142
142
143 @pytest.mark.backends("git", "hg")
143 @pytest.mark.backends("git", "hg")
144 def test_api_comment_pull_request_missing_params_error(self, pr_util):
144 def test_api_comment_pull_request_missing_params_error(self, pr_util):
145 pull_request = pr_util.create_pull_request()
145 pull_request = pr_util.create_pull_request()
146 pull_request_id = pull_request.pull_request_id
146 pull_request_id = pull_request.pull_request_id
147 pull_request_repo = pull_request.target_repo.repo_name
147 pull_request_repo = pull_request.target_repo.repo_name
148 id_, params = build_data(
148 id_, params = build_data(
149 self.apikey, 'comment_pull_request',
149 self.apikey, 'comment_pull_request',
150 repoid=pull_request_repo,
150 repoid=pull_request_repo,
151 pullrequestid=pull_request_id)
151 pullrequestid=pull_request_id)
152 response = api_call(self.app, params)
152 response = api_call(self.app, params)
153
153
154 expected = 'Both message and status parameters are missing. At least one is required.'
154 expected = 'Both message and status parameters are missing. At least one is required.'
155 assert_error(id_, expected, given=response.body)
155 assert_error(id_, expected, given=response.body)
156
156
157 @pytest.mark.backends("git", "hg")
157 @pytest.mark.backends("git", "hg")
158 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
158 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
159 pull_request = pr_util.create_pull_request()
159 pull_request = pr_util.create_pull_request()
160 pull_request_id = pull_request.pull_request_id
160 pull_request_id = pull_request.pull_request_id
161 pull_request_repo = pull_request.target_repo.repo_name
161 pull_request_repo = pull_request.target_repo.repo_name
162 id_, params = build_data(
162 id_, params = build_data(
163 self.apikey, 'comment_pull_request',
163 self.apikey, 'comment_pull_request',
164 repoid=pull_request_repo,
164 repoid=pull_request_repo,
165 pullrequestid=pull_request_id,
165 pullrequestid=pull_request_id,
166 status='42')
166 status='42')
167 response = api_call(self.app, params)
167 response = api_call(self.app, params)
168
168
169 expected = 'Unknown comment status: `42`'
169 expected = 'Unknown comment status: `42`'
170 assert_error(id_, expected, given=response.body)
170 assert_error(id_, expected, given=response.body)
171
171
172 @pytest.mark.backends("git", "hg")
172 @pytest.mark.backends("git", "hg")
173 def test_api_comment_pull_request_repo_error(self, pr_util):
173 def test_api_comment_pull_request_repo_error(self, pr_util):
174 pull_request = pr_util.create_pull_request()
174 pull_request = pr_util.create_pull_request()
175 id_, params = build_data(
175 id_, params = build_data(
176 self.apikey, 'comment_pull_request',
176 self.apikey, 'comment_pull_request',
177 repoid=666, pullrequestid=pull_request.pull_request_id)
177 repoid=666, pullrequestid=pull_request.pull_request_id)
178 response = api_call(self.app, params)
178 response = api_call(self.app, params)
179
179
180 expected = 'repository `666` does not exist'
180 expected = 'repository `666` does not exist'
181 assert_error(id_, expected, given=response.body)
181 assert_error(id_, expected, given=response.body)
182
182
183 @pytest.mark.backends("git", "hg")
183 @pytest.mark.backends("git", "hg")
184 def test_api_comment_pull_request_non_admin_with_userid_error(
184 def test_api_comment_pull_request_non_admin_with_userid_error(
185 self, pr_util):
185 self, pr_util):
186 pull_request = pr_util.create_pull_request()
186 pull_request = pr_util.create_pull_request()
187 id_, params = build_data(
187 id_, params = build_data(
188 self.apikey_regular, 'comment_pull_request',
188 self.apikey_regular, 'comment_pull_request',
189 repoid=pull_request.target_repo.repo_name,
189 repoid=pull_request.target_repo.repo_name,
190 pullrequestid=pull_request.pull_request_id,
190 pullrequestid=pull_request.pull_request_id,
191 userid=TEST_USER_ADMIN_LOGIN)
191 userid=TEST_USER_ADMIN_LOGIN)
192 response = api_call(self.app, params)
192 response = api_call(self.app, params)
193
193
194 expected = 'userid is not the same as your user'
194 expected = 'userid is not the same as your user'
195 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
196
196
197 @pytest.mark.backends("git", "hg")
197 @pytest.mark.backends("git", "hg")
198 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
198 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
199 pull_request = pr_util.create_pull_request()
199 pull_request = pr_util.create_pull_request()
200 id_, params = build_data(
200 id_, params = build_data(
201 self.apikey_regular, 'comment_pull_request',
201 self.apikey_regular, 'comment_pull_request',
202 repoid=pull_request.target_repo.repo_name,
202 repoid=pull_request.target_repo.repo_name,
203 status='approved',
203 status='approved',
204 pullrequestid=pull_request.pull_request_id,
204 pullrequestid=pull_request.pull_request_id,
205 commit_id='XXX')
205 commit_id='XXX')
206 response = api_call(self.app, params)
206 response = api_call(self.app, params)
207
207
208 expected = 'Invalid commit_id `XXX` for this pull request.'
208 expected = 'Invalid commit_id `XXX` for this pull request.'
209 assert_error(id_, expected, given=response.body)
209 assert_error(id_, expected, given=response.body)
@@ -1,102 +1,102 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import Gist
24 from rhodecode.model.db import Gist
25 from rhodecode.model.gist import GistModel
25 from rhodecode.model.gist import GistModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestApiCreateGist(object):
32 class TestApiCreateGist(object):
33 @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [
33 @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [
34 (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC),
34 (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC),
35 (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE),
35 (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE),
36 (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC),
36 (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC),
37 (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE),
37 (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE),
38 ])
38 ])
39 def test_api_create_gist(self, lifetime, gist_type, gist_acl_level):
39 def test_api_create_gist(self, lifetime, gist_type, gist_acl_level):
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey_regular, 'create_gist',
41 self.apikey_regular, 'create_gist',
42 lifetime=lifetime,
42 lifetime=lifetime,
43 description='foobar-gist',
43 description='foobar-gist',
44 gist_type=gist_type,
44 gist_type=gist_type,
45 acl_level=gist_acl_level,
45 acl_level=gist_acl_level,
46 files={'foobar_ąć': {'content': 'foo'}})
46 files={'foobar_ąć': {'content': 'foo'}})
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48 response_json = response.json
48 response_json = response.json
49 gist = response_json['result']['gist']
49 gist = response_json['result']['gist']
50 expected = {
50 expected = {
51 'gist': {
51 'gist': {
52 'access_id': gist['access_id'],
52 'access_id': gist['access_id'],
53 'created_on': gist['created_on'],
53 'created_on': gist['created_on'],
54 'modified_at': gist['modified_at'],
54 'modified_at': gist['modified_at'],
55 'description': 'foobar-gist',
55 'description': 'foobar-gist',
56 'expires': gist['expires'],
56 'expires': gist['expires'],
57 'gist_id': gist['gist_id'],
57 'gist_id': gist['gist_id'],
58 'type': gist_type,
58 'type': gist_type,
59 'url': gist['url'],
59 'url': gist['url'],
60 # content is empty since we don't show it here
60 # content is empty since we don't show it here
61 'content': None,
61 'content': None,
62 'acl_level': gist_acl_level,
62 'acl_level': gist_acl_level,
63 },
63 },
64 'msg': 'created new gist'
64 'msg': 'created new gist'
65 }
65 }
66 try:
66 try:
67 assert_ok(id_, expected, given=response.body)
67 assert_ok(id_, expected, given=response.body)
68 finally:
68 finally:
69 Fixture().destroy_gists()
69 Fixture().destroy_gists()
70
70
71 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
71 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
72 ({'gist_type': '"ups" is not one of private, public'},
72 ({'gist_type': '"ups" is not one of private, public'},
73 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
73 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
74
74
75 ({'lifetime': '-120 is less than minimum value -1'},
75 ({'lifetime': '-120 is less than minimum value -1'},
76 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
76 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
77
77
78 ({'0.content': 'Required'},
78 ({'0.content': 'Required'},
79 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
79 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
80 ])
80 ])
81 def test_api_try_create_gist(
81 def test_api_try_create_gist(
82 self, expected, lifetime, gist_type, gist_acl_level, files):
82 self, expected, lifetime, gist_type, gist_acl_level, files):
83 id_, params = build_data(
83 id_, params = build_data(
84 self.apikey_regular, 'create_gist',
84 self.apikey_regular, 'create_gist',
85 lifetime=lifetime,
85 lifetime=lifetime,
86 description='foobar-gist',
86 description='foobar-gist',
87 gist_type=gist_type,
87 gist_type=gist_type,
88 acl_level=gist_acl_level,
88 acl_level=gist_acl_level,
89 files=files)
89 files=files)
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91
91
92 try:
92 try:
93 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
94 finally:
94 finally:
95 Fixture().destroy_gists()
95 Fixture().destroy_gists()
96
96
97 @mock.patch.object(GistModel, 'create', crash)
97 @mock.patch.object(GistModel, 'create', crash)
98 def test_api_create_gist_exception_occurred(self):
98 def test_api_create_gist_exception_occurred(self):
99 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
99 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
100 response = api_call(self.app, params)
100 response = api_call(self.app, params)
101 expected = 'failed to create gist'
101 expected = 'failed to create gist'
102 assert_error(id_, expected, given=response.body)
102 assert_error(id_, expected, given=response.body)
@@ -1,297 +1,297 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import User
23 from rhodecode.model.db import User
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
28 from rhodecode.api.tests.utils import build_data, api_call, assert_error
28 from rhodecode.api.tests.utils import build_data, api_call, assert_error
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestCreatePullRequestApi(object):
32 class TestCreatePullRequestApi(object):
33 finalizers = []
33 finalizers = []
34
34
35 def teardown_method(self, method):
35 def teardown_method(self, method):
36 if self.finalizers:
36 if self.finalizers:
37 for finalizer in self.finalizers:
37 for finalizer in self.finalizers:
38 finalizer()
38 finalizer()
39 self.finalizers = []
39 self.finalizers = []
40
40
41 def test_create_with_wrong_data(self):
41 def test_create_with_wrong_data(self):
42 required_data = {
42 required_data = {
43 'source_repo': 'tests/source_repo',
43 'source_repo': 'tests/source_repo',
44 'target_repo': 'tests/target_repo',
44 'target_repo': 'tests/target_repo',
45 'source_ref': 'branch:default:initial',
45 'source_ref': 'branch:default:initial',
46 'target_ref': 'branch:default:new-feature',
46 'target_ref': 'branch:default:new-feature',
47 'title': 'Test PR 1'
47 'title': 'Test PR 1'
48 }
48 }
49 for key in required_data:
49 for key in required_data:
50 data = required_data.copy()
50 data = required_data.copy()
51 data.pop(key)
51 data.pop(key)
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'create_pull_request', **data)
53 self.apikey, 'create_pull_request', **data)
54 response = api_call(self.app, params)
54 response = api_call(self.app, params)
55
55
56 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
56 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
58
58
59 @pytest.mark.backends("git", "hg")
59 @pytest.mark.backends("git", "hg")
60 def test_create_with_correct_data(self, backend):
60 def test_create_with_correct_data(self, backend):
61 data = self._prepare_data(backend)
61 data = self._prepare_data(backend)
62 RepoModel().revoke_user_permission(
62 RepoModel().revoke_user_permission(
63 self.source.repo_name, User.DEFAULT_USER)
63 self.source.repo_name, User.DEFAULT_USER)
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey_regular, 'create_pull_request', **data)
65 self.apikey_regular, 'create_pull_request', **data)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67 expected_message = "Created new pull request `{title}`".format(
67 expected_message = "Created new pull request `{title}`".format(
68 title=data['title'])
68 title=data['title'])
69 result = response.json
69 result = response.json
70 assert result['result']['msg'] == expected_message
70 assert result['result']['msg'] == expected_message
71 pull_request_id = result['result']['pull_request_id']
71 pull_request_id = result['result']['pull_request_id']
72 pull_request = PullRequestModel().get(pull_request_id)
72 pull_request = PullRequestModel().get(pull_request_id)
73 assert pull_request.title == data['title']
73 assert pull_request.title == data['title']
74 assert pull_request.description == data['description']
74 assert pull_request.description == data['description']
75 assert pull_request.source_ref == data['source_ref']
75 assert pull_request.source_ref == data['source_ref']
76 assert pull_request.target_ref == data['target_ref']
76 assert pull_request.target_ref == data['target_ref']
77 assert pull_request.source_repo.repo_name == data['source_repo']
77 assert pull_request.source_repo.repo_name == data['source_repo']
78 assert pull_request.target_repo.repo_name == data['target_repo']
78 assert pull_request.target_repo.repo_name == data['target_repo']
79 assert pull_request.revisions == [self.commit_ids['change']]
79 assert pull_request.revisions == [self.commit_ids['change']]
80 assert len(pull_request.reviewers) == 1
80 assert len(pull_request.reviewers) == 1
81
81
82 @pytest.mark.backends("git", "hg")
82 @pytest.mark.backends("git", "hg")
83 def test_create_with_empty_description(self, backend):
83 def test_create_with_empty_description(self, backend):
84 data = self._prepare_data(backend)
84 data = self._prepare_data(backend)
85 data.pop('description')
85 data.pop('description')
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey_regular, 'create_pull_request', **data)
87 self.apikey_regular, 'create_pull_request', **data)
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89 expected_message = "Created new pull request `{title}`".format(
89 expected_message = "Created new pull request `{title}`".format(
90 title=data['title'])
90 title=data['title'])
91 result = response.json
91 result = response.json
92 assert result['result']['msg'] == expected_message
92 assert result['result']['msg'] == expected_message
93 pull_request_id = result['result']['pull_request_id']
93 pull_request_id = result['result']['pull_request_id']
94 pull_request = PullRequestModel().get(pull_request_id)
94 pull_request = PullRequestModel().get(pull_request_id)
95 assert pull_request.description == ''
95 assert pull_request.description == ''
96
96
97 @pytest.mark.backends("git", "hg")
97 @pytest.mark.backends("git", "hg")
98 def test_create_with_reviewers_specified_by_names(
98 def test_create_with_reviewers_specified_by_names(
99 self, backend, no_notifications):
99 self, backend, no_notifications):
100 data = self._prepare_data(backend)
100 data = self._prepare_data(backend)
101 reviewers = [
101 reviewers = [
102 {'username': TEST_USER_REGULAR_LOGIN,
102 {'username': TEST_USER_REGULAR_LOGIN,
103 'reasons': ['added manually']},
103 'reasons': ['added manually']},
104 {'username': TEST_USER_ADMIN_LOGIN,
104 {'username': TEST_USER_ADMIN_LOGIN,
105 'reasons': ['added manually']},
105 'reasons': ['added manually']},
106 ]
106 ]
107 data['reviewers'] = reviewers
107 data['reviewers'] = reviewers
108 id_, params = build_data(
108 id_, params = build_data(
109 self.apikey_regular, 'create_pull_request', **data)
109 self.apikey_regular, 'create_pull_request', **data)
110 response = api_call(self.app, params)
110 response = api_call(self.app, params)
111
111
112 expected_message = "Created new pull request `{title}`".format(
112 expected_message = "Created new pull request `{title}`".format(
113 title=data['title'])
113 title=data['title'])
114 result = response.json
114 result = response.json
115 assert result['result']['msg'] == expected_message
115 assert result['result']['msg'] == expected_message
116 pull_request_id = result['result']['pull_request_id']
116 pull_request_id = result['result']['pull_request_id']
117 pull_request = PullRequestModel().get(pull_request_id)
117 pull_request = PullRequestModel().get(pull_request_id)
118 actual_reviewers = [
118 actual_reviewers = [
119 {'username': r.user.username,
119 {'username': r.user.username,
120 'reasons': ['added manually'],
120 'reasons': ['added manually'],
121 } for r in pull_request.reviewers
121 } for r in pull_request.reviewers
122 ]
122 ]
123 assert sorted(actual_reviewers) == sorted(reviewers)
123 assert sorted(actual_reviewers) == sorted(reviewers)
124
124
125 @pytest.mark.backends("git", "hg")
125 @pytest.mark.backends("git", "hg")
126 def test_create_with_reviewers_specified_by_ids(
126 def test_create_with_reviewers_specified_by_ids(
127 self, backend, no_notifications):
127 self, backend, no_notifications):
128 data = self._prepare_data(backend)
128 data = self._prepare_data(backend)
129 reviewers = [
129 reviewers = [
130 {'username': UserModel().get_by_username(
130 {'username': UserModel().get_by_username(
131 TEST_USER_REGULAR_LOGIN).user_id,
131 TEST_USER_REGULAR_LOGIN).user_id,
132 'reasons': ['added manually']},
132 'reasons': ['added manually']},
133 {'username': UserModel().get_by_username(
133 {'username': UserModel().get_by_username(
134 TEST_USER_ADMIN_LOGIN).user_id,
134 TEST_USER_ADMIN_LOGIN).user_id,
135 'reasons': ['added manually']},
135 'reasons': ['added manually']},
136 ]
136 ]
137
137
138 data['reviewers'] = reviewers
138 data['reviewers'] = reviewers
139 id_, params = build_data(
139 id_, params = build_data(
140 self.apikey_regular, 'create_pull_request', **data)
140 self.apikey_regular, 'create_pull_request', **data)
141 response = api_call(self.app, params)
141 response = api_call(self.app, params)
142
142
143 expected_message = "Created new pull request `{title}`".format(
143 expected_message = "Created new pull request `{title}`".format(
144 title=data['title'])
144 title=data['title'])
145 result = response.json
145 result = response.json
146 assert result['result']['msg'] == expected_message
146 assert result['result']['msg'] == expected_message
147 pull_request_id = result['result']['pull_request_id']
147 pull_request_id = result['result']['pull_request_id']
148 pull_request = PullRequestModel().get(pull_request_id)
148 pull_request = PullRequestModel().get(pull_request_id)
149 actual_reviewers = [
149 actual_reviewers = [
150 {'username': r.user.user_id,
150 {'username': r.user.user_id,
151 'reasons': ['added manually'],
151 'reasons': ['added manually'],
152 } for r in pull_request.reviewers
152 } for r in pull_request.reviewers
153 ]
153 ]
154 assert sorted(actual_reviewers) == sorted(reviewers)
154 assert sorted(actual_reviewers) == sorted(reviewers)
155
155
156 @pytest.mark.backends("git", "hg")
156 @pytest.mark.backends("git", "hg")
157 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
157 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
158 data = self._prepare_data(backend)
158 data = self._prepare_data(backend)
159 data['reviewers'] = [{'username': 'somebody'}]
159 data['reviewers'] = [{'username': 'somebody'}]
160 id_, params = build_data(
160 id_, params = build_data(
161 self.apikey_regular, 'create_pull_request', **data)
161 self.apikey_regular, 'create_pull_request', **data)
162 response = api_call(self.app, params)
162 response = api_call(self.app, params)
163 expected_message = 'user `somebody` does not exist'
163 expected_message = 'user `somebody` does not exist'
164 assert_error(id_, expected_message, given=response.body)
164 assert_error(id_, expected_message, given=response.body)
165
165
166 @pytest.mark.backends("git", "hg")
166 @pytest.mark.backends("git", "hg")
167 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
167 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
168 data = self._prepare_data(backend)
168 data = self._prepare_data(backend)
169 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
169 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
170 data['reviewers'] = reviewers
170 data['reviewers'] = reviewers
171 id_, params = build_data(
171 id_, params = build_data(
172 self.apikey_regular, 'create_pull_request', **data)
172 self.apikey_regular, 'create_pull_request', **data)
173 response = api_call(self.app, params)
173 response = api_call(self.app, params)
174 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
174 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
175 assert_error(id_, expected_message, given=response.body)
175 assert_error(id_, expected_message, given=response.body)
176
176
177 @pytest.mark.backends("git", "hg")
177 @pytest.mark.backends("git", "hg")
178 def test_create_with_no_commit_hashes(self, backend):
178 def test_create_with_no_commit_hashes(self, backend):
179 data = self._prepare_data(backend)
179 data = self._prepare_data(backend)
180 expected_source_ref = data['source_ref']
180 expected_source_ref = data['source_ref']
181 expected_target_ref = data['target_ref']
181 expected_target_ref = data['target_ref']
182 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
182 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
183 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
183 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
184 id_, params = build_data(
184 id_, params = build_data(
185 self.apikey_regular, 'create_pull_request', **data)
185 self.apikey_regular, 'create_pull_request', **data)
186 response = api_call(self.app, params)
186 response = api_call(self.app, params)
187 expected_message = "Created new pull request `{title}`".format(
187 expected_message = "Created new pull request `{title}`".format(
188 title=data['title'])
188 title=data['title'])
189 result = response.json
189 result = response.json
190 assert result['result']['msg'] == expected_message
190 assert result['result']['msg'] == expected_message
191 pull_request_id = result['result']['pull_request_id']
191 pull_request_id = result['result']['pull_request_id']
192 pull_request = PullRequestModel().get(pull_request_id)
192 pull_request = PullRequestModel().get(pull_request_id)
193 assert pull_request.source_ref == expected_source_ref
193 assert pull_request.source_ref == expected_source_ref
194 assert pull_request.target_ref == expected_target_ref
194 assert pull_request.target_ref == expected_target_ref
195
195
196 @pytest.mark.backends("git", "hg")
196 @pytest.mark.backends("git", "hg")
197 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
197 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
198 def test_create_fails_with_wrong_repo(self, backend, data_key):
198 def test_create_fails_with_wrong_repo(self, backend, data_key):
199 repo_name = 'fake-repo'
199 repo_name = 'fake-repo'
200 data = self._prepare_data(backend)
200 data = self._prepare_data(backend)
201 data[data_key] = repo_name
201 data[data_key] = repo_name
202 id_, params = build_data(
202 id_, params = build_data(
203 self.apikey_regular, 'create_pull_request', **data)
203 self.apikey_regular, 'create_pull_request', **data)
204 response = api_call(self.app, params)
204 response = api_call(self.app, params)
205 expected_message = 'repository `{}` does not exist'.format(repo_name)
205 expected_message = 'repository `{}` does not exist'.format(repo_name)
206 assert_error(id_, expected_message, given=response.body)
206 assert_error(id_, expected_message, given=response.body)
207
207
208 @pytest.mark.backends("git", "hg")
208 @pytest.mark.backends("git", "hg")
209 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
209 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
210 def test_create_fails_with_non_existing_branch(self, backend, data_key):
210 def test_create_fails_with_non_existing_branch(self, backend, data_key):
211 branch_name = 'test-branch'
211 branch_name = 'test-branch'
212 data = self._prepare_data(backend)
212 data = self._prepare_data(backend)
213 data[data_key] = "branch:{}".format(branch_name)
213 data[data_key] = "branch:{}".format(branch_name)
214 id_, params = build_data(
214 id_, params = build_data(
215 self.apikey_regular, 'create_pull_request', **data)
215 self.apikey_regular, 'create_pull_request', **data)
216 response = api_call(self.app, params)
216 response = api_call(self.app, params)
217 expected_message = 'The specified branch `{}` does not exist'.format(
217 expected_message = 'The specified branch `{}` does not exist'.format(
218 branch_name)
218 branch_name)
219 assert_error(id_, expected_message, given=response.body)
219 assert_error(id_, expected_message, given=response.body)
220
220
221 @pytest.mark.backends("git", "hg")
221 @pytest.mark.backends("git", "hg")
222 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
222 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
223 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
223 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
224 data = self._prepare_data(backend)
224 data = self._prepare_data(backend)
225 ref = 'stange-ref'
225 ref = 'stange-ref'
226 data[data_key] = ref
226 data[data_key] = ref
227 id_, params = build_data(
227 id_, params = build_data(
228 self.apikey_regular, 'create_pull_request', **data)
228 self.apikey_regular, 'create_pull_request', **data)
229 response = api_call(self.app, params)
229 response = api_call(self.app, params)
230 expected_message = (
230 expected_message = (
231 'Ref `{ref}` given in a wrong format. Please check the API'
231 'Ref `{ref}` given in a wrong format. Please check the API'
232 ' documentation for more details'.format(ref=ref))
232 ' documentation for more details'.format(ref=ref))
233 assert_error(id_, expected_message, given=response.body)
233 assert_error(id_, expected_message, given=response.body)
234
234
235 @pytest.mark.backends("git", "hg")
235 @pytest.mark.backends("git", "hg")
236 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
236 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
237 def test_create_fails_with_non_existing_ref(self, backend, data_key):
237 def test_create_fails_with_non_existing_ref(self, backend, data_key):
238 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
238 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
239 ref = self._get_full_ref(backend, commit_id)
239 ref = self._get_full_ref(backend, commit_id)
240 data = self._prepare_data(backend)
240 data = self._prepare_data(backend)
241 data[data_key] = ref
241 data[data_key] = ref
242 id_, params = build_data(
242 id_, params = build_data(
243 self.apikey_regular, 'create_pull_request', **data)
243 self.apikey_regular, 'create_pull_request', **data)
244 response = api_call(self.app, params)
244 response = api_call(self.app, params)
245 expected_message = 'Ref `{}` does not exist'.format(ref)
245 expected_message = 'Ref `{}` does not exist'.format(ref)
246 assert_error(id_, expected_message, given=response.body)
246 assert_error(id_, expected_message, given=response.body)
247
247
248 @pytest.mark.backends("git", "hg")
248 @pytest.mark.backends("git", "hg")
249 def test_create_fails_when_no_revisions(self, backend):
249 def test_create_fails_when_no_revisions(self, backend):
250 data = self._prepare_data(backend, source_head='initial')
250 data = self._prepare_data(backend, source_head='initial')
251 id_, params = build_data(
251 id_, params = build_data(
252 self.apikey_regular, 'create_pull_request', **data)
252 self.apikey_regular, 'create_pull_request', **data)
253 response = api_call(self.app, params)
253 response = api_call(self.app, params)
254 expected_message = 'no commits found'
254 expected_message = 'no commits found'
255 assert_error(id_, expected_message, given=response.body)
255 assert_error(id_, expected_message, given=response.body)
256
256
257 @pytest.mark.backends("git", "hg")
257 @pytest.mark.backends("git", "hg")
258 def test_create_fails_when_no_permissions(self, backend):
258 def test_create_fails_when_no_permissions(self, backend):
259 data = self._prepare_data(backend)
259 data = self._prepare_data(backend)
260 RepoModel().revoke_user_permission(
260 RepoModel().revoke_user_permission(
261 self.source.repo_name, User.DEFAULT_USER)
261 self.source.repo_name, User.DEFAULT_USER)
262 RepoModel().revoke_user_permission(
262 RepoModel().revoke_user_permission(
263 self.source.repo_name, self.test_user)
263 self.source.repo_name, self.test_user)
264 id_, params = build_data(
264 id_, params = build_data(
265 self.apikey_regular, 'create_pull_request', **data)
265 self.apikey_regular, 'create_pull_request', **data)
266 response = api_call(self.app, params)
266 response = api_call(self.app, params)
267 expected_message = 'repository `{}` does not exist'.format(
267 expected_message = 'repository `{}` does not exist'.format(
268 self.source.repo_name)
268 self.source.repo_name)
269 assert_error(id_, expected_message, given=response.body)
269 assert_error(id_, expected_message, given=response.body)
270
270
271 def _prepare_data(
271 def _prepare_data(
272 self, backend, source_head='change', target_head='initial'):
272 self, backend, source_head='change', target_head='initial'):
273 commits = [
273 commits = [
274 {'message': 'initial'},
274 {'message': 'initial'},
275 {'message': 'change'},
275 {'message': 'change'},
276 {'message': 'new-feature', 'parents': ['initial']},
276 {'message': 'new-feature', 'parents': ['initial']},
277 ]
277 ]
278 self.commit_ids = backend.create_master_repo(commits)
278 self.commit_ids = backend.create_master_repo(commits)
279 self.source = backend.create_repo(heads=[source_head])
279 self.source = backend.create_repo(heads=[source_head])
280 self.target = backend.create_repo(heads=[target_head])
280 self.target = backend.create_repo(heads=[target_head])
281 data = {
281 data = {
282 'source_repo': self.source.repo_name,
282 'source_repo': self.source.repo_name,
283 'target_repo': self.target.repo_name,
283 'target_repo': self.target.repo_name,
284 'source_ref': self._get_full_ref(
284 'source_ref': self._get_full_ref(
285 backend, self.commit_ids[source_head]),
285 backend, self.commit_ids[source_head]),
286 'target_ref': self._get_full_ref(
286 'target_ref': self._get_full_ref(
287 backend, self.commit_ids[target_head]),
287 backend, self.commit_ids[target_head]),
288 'title': 'Test PR 1',
288 'title': 'Test PR 1',
289 'description': 'Test'
289 'description': 'Test'
290 }
290 }
291 RepoModel().grant_user_permission(
291 RepoModel().grant_user_permission(
292 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
292 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
293 return data
293 return data
294
294
295 def _get_full_ref(self, backend, commit_id):
295 def _get_full_ref(self, backend, commit_id):
296 return 'branch:{branch}:{commit_id}'.format(
296 return 'branch:{branch}:{commit_id}'.format(
297 branch=backend.default_branch_name, commit_id=commit_id)
297 branch=backend.default_branch_name, commit_id=commit_id)
@@ -1,350 +1,350 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import json
21 import json
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.lib.utils2 import safe_unicode
26 from rhodecode.lib.utils2 import safe_unicode
27 from rhodecode.lib.vcs import settings
27 from rhodecode.lib.vcs import settings
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.repo import RepoModel
29 from rhodecode.model.repo import RepoModel
30 from rhodecode.model.user import UserModel
30 from rhodecode.model.user import UserModel
31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
32 from rhodecode.api.tests.utils import (
32 from rhodecode.api.tests.utils import (
33 build_data, api_call, assert_ok, assert_error, crash)
33 build_data, api_call, assert_ok, assert_error, crash)
34 from rhodecode.tests.fixture import Fixture
34 from rhodecode.tests.fixture import Fixture
35
35
36
36
37 fixture = Fixture()
37 fixture = Fixture()
38
38
39
39
40 @pytest.mark.usefixtures("testuser_api", "app")
40 @pytest.mark.usefixtures("testuser_api", "app")
41 class TestCreateRepo(object):
41 class TestCreateRepo(object):
42
42
43 @pytest.mark.parametrize('given, expected_name, expected_exc', [
43 @pytest.mark.parametrize('given, expected_name, expected_exc', [
44 ('api repo-1', 'api-repo-1', False),
44 ('api repo-1', 'api-repo-1', False),
45 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
45 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
46 (u'unicode-ąć', u'unicode-ąć', False),
46 (u'unicode-ąć', u'unicode-ąć', False),
47 ('some repo v1.2', 'some-repo-v1.2', False),
47 ('some repo v1.2', 'some-repo-v1.2', False),
48 ('v2.0', 'v2.0', False),
48 ('v2.0', 'v2.0', False),
49 ])
49 ])
50 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
50 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
51
51
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey,
53 self.apikey,
54 'create_repo',
54 'create_repo',
55 repo_name=given,
55 repo_name=given,
56 owner=TEST_USER_ADMIN_LOGIN,
56 owner=TEST_USER_ADMIN_LOGIN,
57 repo_type=backend.alias,
57 repo_type=backend.alias,
58 )
58 )
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 ret = {
61 ret = {
62 'msg': 'Created new repository `%s`' % (expected_name,),
62 'msg': 'Created new repository `%s`' % (expected_name,),
63 'success': True,
63 'success': True,
64 'task': None,
64 'task': None,
65 }
65 }
66 expected = ret
66 expected = ret
67 assert_ok(id_, expected, given=response.body)
67 assert_ok(id_, expected, given=response.body)
68
68
69 repo = RepoModel().get_by_repo_name(safe_unicode(expected_name))
69 repo = RepoModel().get_by_repo_name(safe_unicode(expected_name))
70 assert repo is not None
70 assert repo is not None
71
71
72 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
72 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
73 response = api_call(self.app, params)
73 response = api_call(self.app, params)
74 body = json.loads(response.body)
74 body = json.loads(response.body)
75
75
76 assert body['result']['enable_downloads'] is False
76 assert body['result']['enable_downloads'] is False
77 assert body['result']['enable_locking'] is False
77 assert body['result']['enable_locking'] is False
78 assert body['result']['enable_statistics'] is False
78 assert body['result']['enable_statistics'] is False
79
79
80 fixture.destroy_repo(safe_unicode(expected_name))
80 fixture.destroy_repo(safe_unicode(expected_name))
81
81
82 def test_api_create_restricted_repo_type(self, backend):
82 def test_api_create_restricted_repo_type(self, backend):
83 repo_name = 'api-repo-type-{0}'.format(backend.alias)
83 repo_name = 'api-repo-type-{0}'.format(backend.alias)
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey,
85 self.apikey,
86 'create_repo',
86 'create_repo',
87 repo_name=repo_name,
87 repo_name=repo_name,
88 owner=TEST_USER_ADMIN_LOGIN,
88 owner=TEST_USER_ADMIN_LOGIN,
89 repo_type=backend.alias,
89 repo_type=backend.alias,
90 )
90 )
91 git_backend = settings.BACKENDS['git']
91 git_backend = settings.BACKENDS['git']
92 with mock.patch(
92 with mock.patch(
93 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
93 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
94 response = api_call(self.app, params)
94 response = api_call(self.app, params)
95
95
96 repo = RepoModel().get_by_repo_name(repo_name)
96 repo = RepoModel().get_by_repo_name(repo_name)
97
97
98 if backend.alias == 'git':
98 if backend.alias == 'git':
99 assert repo is not None
99 assert repo is not None
100 expected = {
100 expected = {
101 'msg': 'Created new repository `{0}`'.format(repo_name,),
101 'msg': 'Created new repository `{0}`'.format(repo_name,),
102 'success': True,
102 'success': True,
103 'task': None,
103 'task': None,
104 }
104 }
105 assert_ok(id_, expected, given=response.body)
105 assert_ok(id_, expected, given=response.body)
106 else:
106 else:
107 assert repo is None
107 assert repo is None
108
108
109 fixture.destroy_repo(repo_name)
109 fixture.destroy_repo(repo_name)
110
110
111 def test_api_create_repo_with_booleans(self, backend):
111 def test_api_create_repo_with_booleans(self, backend):
112 repo_name = 'api-repo-2'
112 repo_name = 'api-repo-2'
113 id_, params = build_data(
113 id_, params = build_data(
114 self.apikey,
114 self.apikey,
115 'create_repo',
115 'create_repo',
116 repo_name=repo_name,
116 repo_name=repo_name,
117 owner=TEST_USER_ADMIN_LOGIN,
117 owner=TEST_USER_ADMIN_LOGIN,
118 repo_type=backend.alias,
118 repo_type=backend.alias,
119 enable_statistics=True,
119 enable_statistics=True,
120 enable_locking=True,
120 enable_locking=True,
121 enable_downloads=True
121 enable_downloads=True
122 )
122 )
123 response = api_call(self.app, params)
123 response = api_call(self.app, params)
124
124
125 repo = RepoModel().get_by_repo_name(repo_name)
125 repo = RepoModel().get_by_repo_name(repo_name)
126
126
127 assert repo is not None
127 assert repo is not None
128 ret = {
128 ret = {
129 'msg': 'Created new repository `%s`' % (repo_name,),
129 'msg': 'Created new repository `%s`' % (repo_name,),
130 'success': True,
130 'success': True,
131 'task': None,
131 'task': None,
132 }
132 }
133 expected = ret
133 expected = ret
134 assert_ok(id_, expected, given=response.body)
134 assert_ok(id_, expected, given=response.body)
135
135
136 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
136 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
137 response = api_call(self.app, params)
137 response = api_call(self.app, params)
138 body = json.loads(response.body)
138 body = json.loads(response.body)
139
139
140 assert body['result']['enable_downloads'] is True
140 assert body['result']['enable_downloads'] is True
141 assert body['result']['enable_locking'] is True
141 assert body['result']['enable_locking'] is True
142 assert body['result']['enable_statistics'] is True
142 assert body['result']['enable_statistics'] is True
143
143
144 fixture.destroy_repo(repo_name)
144 fixture.destroy_repo(repo_name)
145
145
146 def test_api_create_repo_in_group(self, backend):
146 def test_api_create_repo_in_group(self, backend):
147 repo_group_name = 'my_gr'
147 repo_group_name = 'my_gr'
148 # create the parent
148 # create the parent
149 fixture.create_repo_group(repo_group_name)
149 fixture.create_repo_group(repo_group_name)
150
150
151 repo_name = '%s/api-repo-gr' % (repo_group_name,)
151 repo_name = '%s/api-repo-gr' % (repo_group_name,)
152 id_, params = build_data(
152 id_, params = build_data(
153 self.apikey, 'create_repo',
153 self.apikey, 'create_repo',
154 repo_name=repo_name,
154 repo_name=repo_name,
155 owner=TEST_USER_ADMIN_LOGIN,
155 owner=TEST_USER_ADMIN_LOGIN,
156 repo_type=backend.alias,)
156 repo_type=backend.alias,)
157 response = api_call(self.app, params)
157 response = api_call(self.app, params)
158 repo = RepoModel().get_by_repo_name(repo_name)
158 repo = RepoModel().get_by_repo_name(repo_name)
159 assert repo is not None
159 assert repo is not None
160 assert repo.group is not None
160 assert repo.group is not None
161
161
162 ret = {
162 ret = {
163 'msg': 'Created new repository `%s`' % (repo_name,),
163 'msg': 'Created new repository `%s`' % (repo_name,),
164 'success': True,
164 'success': True,
165 'task': None,
165 'task': None,
166 }
166 }
167 expected = ret
167 expected = ret
168 assert_ok(id_, expected, given=response.body)
168 assert_ok(id_, expected, given=response.body)
169 fixture.destroy_repo(repo_name)
169 fixture.destroy_repo(repo_name)
170 fixture.destroy_repo_group(repo_group_name)
170 fixture.destroy_repo_group(repo_group_name)
171
171
172 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
172 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
173 repo_group_name = 'fake_group'
173 repo_group_name = 'fake_group'
174
174
175 repo_name = '%s/api-repo-gr' % (repo_group_name,)
175 repo_name = '%s/api-repo-gr' % (repo_group_name,)
176 id_, params = build_data(
176 id_, params = build_data(
177 self.apikey, 'create_repo',
177 self.apikey, 'create_repo',
178 repo_name=repo_name,
178 repo_name=repo_name,
179 owner=TEST_USER_ADMIN_LOGIN,
179 owner=TEST_USER_ADMIN_LOGIN,
180 repo_type=backend.alias,)
180 repo_type=backend.alias,)
181 response = api_call(self.app, params)
181 response = api_call(self.app, params)
182
182
183 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
183 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
184 repo_group_name)}
184 repo_group_name)}
185 assert_error(id_, expected, given=response.body)
185 assert_error(id_, expected, given=response.body)
186
186
187 def test_api_create_repo_unknown_owner(self, backend):
187 def test_api_create_repo_unknown_owner(self, backend):
188 repo_name = 'api-repo-2'
188 repo_name = 'api-repo-2'
189 owner = 'i-dont-exist'
189 owner = 'i-dont-exist'
190 id_, params = build_data(
190 id_, params = build_data(
191 self.apikey, 'create_repo',
191 self.apikey, 'create_repo',
192 repo_name=repo_name,
192 repo_name=repo_name,
193 owner=owner,
193 owner=owner,
194 repo_type=backend.alias)
194 repo_type=backend.alias)
195 response = api_call(self.app, params)
195 response = api_call(self.app, params)
196 expected = 'user `%s` does not exist' % (owner,)
196 expected = 'user `%s` does not exist' % (owner,)
197 assert_error(id_, expected, given=response.body)
197 assert_error(id_, expected, given=response.body)
198
198
199 def test_api_create_repo_dont_specify_owner(self, backend):
199 def test_api_create_repo_dont_specify_owner(self, backend):
200 repo_name = 'api-repo-3'
200 repo_name = 'api-repo-3'
201 id_, params = build_data(
201 id_, params = build_data(
202 self.apikey, 'create_repo',
202 self.apikey, 'create_repo',
203 repo_name=repo_name,
203 repo_name=repo_name,
204 repo_type=backend.alias)
204 repo_type=backend.alias)
205 response = api_call(self.app, params)
205 response = api_call(self.app, params)
206
206
207 repo = RepoModel().get_by_repo_name(repo_name)
207 repo = RepoModel().get_by_repo_name(repo_name)
208 assert repo is not None
208 assert repo is not None
209 ret = {
209 ret = {
210 'msg': 'Created new repository `%s`' % (repo_name,),
210 'msg': 'Created new repository `%s`' % (repo_name,),
211 'success': True,
211 'success': True,
212 'task': None,
212 'task': None,
213 }
213 }
214 expected = ret
214 expected = ret
215 assert_ok(id_, expected, given=response.body)
215 assert_ok(id_, expected, given=response.body)
216 fixture.destroy_repo(repo_name)
216 fixture.destroy_repo(repo_name)
217
217
218 def test_api_create_repo_by_non_admin(self, backend):
218 def test_api_create_repo_by_non_admin(self, backend):
219 repo_name = 'api-repo-4'
219 repo_name = 'api-repo-4'
220 id_, params = build_data(
220 id_, params = build_data(
221 self.apikey_regular, 'create_repo',
221 self.apikey_regular, 'create_repo',
222 repo_name=repo_name,
222 repo_name=repo_name,
223 repo_type=backend.alias)
223 repo_type=backend.alias)
224 response = api_call(self.app, params)
224 response = api_call(self.app, params)
225
225
226 repo = RepoModel().get_by_repo_name(repo_name)
226 repo = RepoModel().get_by_repo_name(repo_name)
227 assert repo is not None
227 assert repo is not None
228 ret = {
228 ret = {
229 'msg': 'Created new repository `%s`' % (repo_name,),
229 'msg': 'Created new repository `%s`' % (repo_name,),
230 'success': True,
230 'success': True,
231 'task': None,
231 'task': None,
232 }
232 }
233 expected = ret
233 expected = ret
234 assert_ok(id_, expected, given=response.body)
234 assert_ok(id_, expected, given=response.body)
235 fixture.destroy_repo(repo_name)
235 fixture.destroy_repo(repo_name)
236
236
237 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
237 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
238 repo_name = 'api-repo-5'
238 repo_name = 'api-repo-5'
239 owner = 'i-dont-exist'
239 owner = 'i-dont-exist'
240 id_, params = build_data(
240 id_, params = build_data(
241 self.apikey_regular, 'create_repo',
241 self.apikey_regular, 'create_repo',
242 repo_name=repo_name,
242 repo_name=repo_name,
243 repo_type=backend.alias,
243 repo_type=backend.alias,
244 owner=owner)
244 owner=owner)
245 response = api_call(self.app, params)
245 response = api_call(self.app, params)
246
246
247 expected = 'Only RhodeCode super-admin can specify `owner` param'
247 expected = 'Only RhodeCode super-admin can specify `owner` param'
248 assert_error(id_, expected, given=response.body)
248 assert_error(id_, expected, given=response.body)
249 fixture.destroy_repo(repo_name)
249 fixture.destroy_repo(repo_name)
250
250
251 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
251 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
252 repo_group_name = 'no-access'
252 repo_group_name = 'no-access'
253 fixture.create_repo_group(repo_group_name)
253 fixture.create_repo_group(repo_group_name)
254 repo_name = 'no-access/api-repo'
254 repo_name = 'no-access/api-repo'
255
255
256 id_, params = build_data(
256 id_, params = build_data(
257 self.apikey_regular, 'create_repo',
257 self.apikey_regular, 'create_repo',
258 repo_name=repo_name,
258 repo_name=repo_name,
259 repo_type=backend.alias)
259 repo_type=backend.alias)
260 response = api_call(self.app, params)
260 response = api_call(self.app, params)
261
261
262 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
262 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
263 repo_group_name)}
263 repo_group_name)}
264 assert_error(id_, expected, given=response.body)
264 assert_error(id_, expected, given=response.body)
265 fixture.destroy_repo_group(repo_group_name)
265 fixture.destroy_repo_group(repo_group_name)
266 fixture.destroy_repo(repo_name)
266 fixture.destroy_repo(repo_name)
267
267
268 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
268 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
269 self, backend, user_util):
269 self, backend, user_util):
270
270
271 regular_user = user_util.create_user()
271 regular_user = user_util.create_user()
272 regular_user_api_key = regular_user.api_key
272 regular_user_api_key = regular_user.api_key
273
273
274 usr = UserModel().get_by_username(regular_user.username)
274 usr = UserModel().get_by_username(regular_user.username)
275 usr.inherit_default_permissions = False
275 usr.inherit_default_permissions = False
276 Session().add(usr)
276 Session().add(usr)
277
277
278 repo_name = backend.new_repo_name()
278 repo_name = backend.new_repo_name()
279 id_, params = build_data(
279 id_, params = build_data(
280 regular_user_api_key, 'create_repo',
280 regular_user_api_key, 'create_repo',
281 repo_name=repo_name,
281 repo_name=repo_name,
282 repo_type=backend.alias)
282 repo_type=backend.alias)
283 response = api_call(self.app, params)
283 response = api_call(self.app, params)
284 expected = {
284 expected = {
285 "repo_name": "You do not have the permission to "
285 "repo_name": "You do not have the permission to "
286 "store repositories in the root location."}
286 "store repositories in the root location."}
287 assert_error(id_, expected, given=response.body)
287 assert_error(id_, expected, given=response.body)
288
288
289 def test_api_create_repo_exists(self, backend):
289 def test_api_create_repo_exists(self, backend):
290 repo_name = backend.repo_name
290 repo_name = backend.repo_name
291 id_, params = build_data(
291 id_, params = build_data(
292 self.apikey, 'create_repo',
292 self.apikey, 'create_repo',
293 repo_name=repo_name,
293 repo_name=repo_name,
294 owner=TEST_USER_ADMIN_LOGIN,
294 owner=TEST_USER_ADMIN_LOGIN,
295 repo_type=backend.alias,)
295 repo_type=backend.alias,)
296 response = api_call(self.app, params)
296 response = api_call(self.app, params)
297 expected = {
297 expected = {
298 'unique_repo_name': 'Repository with name `{}` already exists'.format(
298 'unique_repo_name': 'Repository with name `{}` already exists'.format(
299 repo_name)}
299 repo_name)}
300 assert_error(id_, expected, given=response.body)
300 assert_error(id_, expected, given=response.body)
301
301
302 @mock.patch.object(RepoModel, 'create', crash)
302 @mock.patch.object(RepoModel, 'create', crash)
303 def test_api_create_repo_exception_occurred(self, backend):
303 def test_api_create_repo_exception_occurred(self, backend):
304 repo_name = 'api-repo-6'
304 repo_name = 'api-repo-6'
305 id_, params = build_data(
305 id_, params = build_data(
306 self.apikey, 'create_repo',
306 self.apikey, 'create_repo',
307 repo_name=repo_name,
307 repo_name=repo_name,
308 owner=TEST_USER_ADMIN_LOGIN,
308 owner=TEST_USER_ADMIN_LOGIN,
309 repo_type=backend.alias,)
309 repo_type=backend.alias,)
310 response = api_call(self.app, params)
310 response = api_call(self.app, params)
311 expected = 'failed to create repository `%s`' % (repo_name,)
311 expected = 'failed to create repository `%s`' % (repo_name,)
312 assert_error(id_, expected, given=response.body)
312 assert_error(id_, expected, given=response.body)
313
313
314 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
314 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
315 (None, 'foo bar x', 'foo-bar-x'),
315 (None, 'foo bar x', 'foo-bar-x'),
316 ('foo', '/foo//bar x', 'foo/bar-x'),
316 ('foo', '/foo//bar x', 'foo/bar-x'),
317 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
317 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
318 ])
318 ])
319 def test_create_repo_with_extra_slashes_in_name(
319 def test_create_repo_with_extra_slashes_in_name(
320 self, backend, parent_group, dirty_name, expected_name):
320 self, backend, parent_group, dirty_name, expected_name):
321
321
322 if parent_group:
322 if parent_group:
323 gr = fixture.create_repo_group(parent_group)
323 gr = fixture.create_repo_group(parent_group)
324 assert gr.group_name == parent_group
324 assert gr.group_name == parent_group
325
325
326 id_, params = build_data(
326 id_, params = build_data(
327 self.apikey, 'create_repo',
327 self.apikey, 'create_repo',
328 repo_name=dirty_name,
328 repo_name=dirty_name,
329 repo_type=backend.alias,
329 repo_type=backend.alias,
330 owner=TEST_USER_ADMIN_LOGIN,)
330 owner=TEST_USER_ADMIN_LOGIN,)
331 response = api_call(self.app, params)
331 response = api_call(self.app, params)
332 expected ={
332 expected ={
333 "msg": "Created new repository `{}`".format(expected_name),
333 "msg": "Created new repository `{}`".format(expected_name),
334 "task": None,
334 "task": None,
335 "success": True
335 "success": True
336 }
336 }
337 assert_ok(id_, expected, response.body)
337 assert_ok(id_, expected, response.body)
338
338
339 repo = RepoModel().get_by_repo_name(expected_name)
339 repo = RepoModel().get_by_repo_name(expected_name)
340 assert repo is not None
340 assert repo is not None
341
341
342 expected = {
342 expected = {
343 'msg': 'Created new repository `%s`' % (expected_name,),
343 'msg': 'Created new repository `%s`' % (expected_name,),
344 'success': True,
344 'success': True,
345 'task': None,
345 'task': None,
346 }
346 }
347 assert_ok(id_, expected, given=response.body)
347 assert_ok(id_, expected, given=response.body)
348 fixture.destroy_repo(expected_name)
348 fixture.destroy_repo(expected_name)
349 if parent_group:
349 if parent_group:
350 fixture.destroy_repo_group(parent_group)
350 fixture.destroy_repo_group(parent_group)
@@ -1,289 +1,289 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, crash)
29 build_data, api_call, assert_ok, assert_error, crash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31
31
32
32
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35
35
36 @pytest.mark.usefixtures("testuser_api", "app")
36 @pytest.mark.usefixtures("testuser_api", "app")
37 class TestCreateRepoGroup(object):
37 class TestCreateRepoGroup(object):
38 def test_api_create_repo_group(self):
38 def test_api_create_repo_group(self):
39 repo_group_name = 'api-repo-group'
39 repo_group_name = 'api-repo-group'
40
40
41 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
41 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
42 assert repo_group is None
42 assert repo_group is None
43
43
44 id_, params = build_data(
44 id_, params = build_data(
45 self.apikey, 'create_repo_group',
45 self.apikey, 'create_repo_group',
46 group_name=repo_group_name,
46 group_name=repo_group_name,
47 owner=TEST_USER_ADMIN_LOGIN,)
47 owner=TEST_USER_ADMIN_LOGIN,)
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
50 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
51 assert repo_group is not None
51 assert repo_group is not None
52 ret = {
52 ret = {
53 'msg': 'Created new repo group `%s`' % (repo_group_name,),
53 'msg': 'Created new repo group `%s`' % (repo_group_name,),
54 'repo_group': repo_group.get_api_data()
54 'repo_group': repo_group.get_api_data()
55 }
55 }
56 expected = ret
56 expected = ret
57 try:
57 try:
58 assert_ok(id_, expected, given=response.body)
58 assert_ok(id_, expected, given=response.body)
59 finally:
59 finally:
60 fixture.destroy_repo_group(repo_group_name)
60 fixture.destroy_repo_group(repo_group_name)
61
61
62 def test_api_create_repo_group_in_another_group(self):
62 def test_api_create_repo_group_in_another_group(self):
63 repo_group_name = 'api-repo-group'
63 repo_group_name = 'api-repo-group'
64
64
65 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
65 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
66 assert repo_group is None
66 assert repo_group is None
67 # create the parent
67 # create the parent
68 fixture.create_repo_group(repo_group_name)
68 fixture.create_repo_group(repo_group_name)
69
69
70 full_repo_group_name = repo_group_name+'/'+repo_group_name
70 full_repo_group_name = repo_group_name+'/'+repo_group_name
71 id_, params = build_data(
71 id_, params = build_data(
72 self.apikey, 'create_repo_group',
72 self.apikey, 'create_repo_group',
73 group_name=full_repo_group_name,
73 group_name=full_repo_group_name,
74 owner=TEST_USER_ADMIN_LOGIN,
74 owner=TEST_USER_ADMIN_LOGIN,
75 copy_permissions=True)
75 copy_permissions=True)
76 response = api_call(self.app, params)
76 response = api_call(self.app, params)
77
77
78 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
78 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
79 assert repo_group is not None
79 assert repo_group is not None
80 ret = {
80 ret = {
81 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
81 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
82 'repo_group': repo_group.get_api_data()
82 'repo_group': repo_group.get_api_data()
83 }
83 }
84 expected = ret
84 expected = ret
85 try:
85 try:
86 assert_ok(id_, expected, given=response.body)
86 assert_ok(id_, expected, given=response.body)
87 finally:
87 finally:
88 fixture.destroy_repo_group(full_repo_group_name)
88 fixture.destroy_repo_group(full_repo_group_name)
89 fixture.destroy_repo_group(repo_group_name)
89 fixture.destroy_repo_group(repo_group_name)
90
90
91 def test_api_create_repo_group_in_another_group_not_existing(self):
91 def test_api_create_repo_group_in_another_group_not_existing(self):
92 repo_group_name = 'api-repo-group-no'
92 repo_group_name = 'api-repo-group-no'
93
93
94 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
94 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
95 assert repo_group is None
95 assert repo_group is None
96
96
97 full_repo_group_name = repo_group_name+'/'+repo_group_name
97 full_repo_group_name = repo_group_name+'/'+repo_group_name
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'create_repo_group',
99 self.apikey, 'create_repo_group',
100 group_name=full_repo_group_name,
100 group_name=full_repo_group_name,
101 owner=TEST_USER_ADMIN_LOGIN,
101 owner=TEST_USER_ADMIN_LOGIN,
102 copy_permissions=True)
102 copy_permissions=True)
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104 expected = {
104 expected = {
105 'repo_group':
105 'repo_group':
106 'Parent repository group `{}` does not exist'.format(
106 'Parent repository group `{}` does not exist'.format(
107 repo_group_name)}
107 repo_group_name)}
108 assert_error(id_, expected, given=response.body)
108 assert_error(id_, expected, given=response.body)
109
109
110 def test_api_create_repo_group_that_exists(self):
110 def test_api_create_repo_group_that_exists(self):
111 repo_group_name = 'api-repo-group'
111 repo_group_name = 'api-repo-group'
112
112
113 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
113 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
114 assert repo_group is None
114 assert repo_group is None
115
115
116 fixture.create_repo_group(repo_group_name)
116 fixture.create_repo_group(repo_group_name)
117 id_, params = build_data(
117 id_, params = build_data(
118 self.apikey, 'create_repo_group',
118 self.apikey, 'create_repo_group',
119 group_name=repo_group_name,
119 group_name=repo_group_name,
120 owner=TEST_USER_ADMIN_LOGIN,)
120 owner=TEST_USER_ADMIN_LOGIN,)
121 response = api_call(self.app, params)
121 response = api_call(self.app, params)
122 expected = {
122 expected = {
123 'unique_repo_group_name':
123 'unique_repo_group_name':
124 'Repository group with name `{}` already exists'.format(
124 'Repository group with name `{}` already exists'.format(
125 repo_group_name)}
125 repo_group_name)}
126 try:
126 try:
127 assert_error(id_, expected, given=response.body)
127 assert_error(id_, expected, given=response.body)
128 finally:
128 finally:
129 fixture.destroy_repo_group(repo_group_name)
129 fixture.destroy_repo_group(repo_group_name)
130
130
131 def test_api_create_repo_group_regular_user_wit_root_location_perms(
131 def test_api_create_repo_group_regular_user_wit_root_location_perms(
132 self, user_util):
132 self, user_util):
133 regular_user = user_util.create_user()
133 regular_user = user_util.create_user()
134 regular_user_api_key = regular_user.api_key
134 regular_user_api_key = regular_user.api_key
135
135
136 repo_group_name = 'api-repo-group-by-regular-user'
136 repo_group_name = 'api-repo-group-by-regular-user'
137
137
138 usr = UserModel().get_by_username(regular_user.username)
138 usr = UserModel().get_by_username(regular_user.username)
139 usr.inherit_default_permissions = False
139 usr.inherit_default_permissions = False
140 Session().add(usr)
140 Session().add(usr)
141
141
142 UserModel().grant_perm(
142 UserModel().grant_perm(
143 regular_user.username, 'hg.repogroup.create.true')
143 regular_user.username, 'hg.repogroup.create.true')
144 Session().commit()
144 Session().commit()
145
145
146 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
146 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
147 assert repo_group is None
147 assert repo_group is None
148
148
149 id_, params = build_data(
149 id_, params = build_data(
150 regular_user_api_key, 'create_repo_group',
150 regular_user_api_key, 'create_repo_group',
151 group_name=repo_group_name)
151 group_name=repo_group_name)
152 response = api_call(self.app, params)
152 response = api_call(self.app, params)
153
153
154 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
154 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
155 assert repo_group is not None
155 assert repo_group is not None
156 expected = {
156 expected = {
157 'msg': 'Created new repo group `%s`' % (repo_group_name,),
157 'msg': 'Created new repo group `%s`' % (repo_group_name,),
158 'repo_group': repo_group.get_api_data()
158 'repo_group': repo_group.get_api_data()
159 }
159 }
160 try:
160 try:
161 assert_ok(id_, expected, given=response.body)
161 assert_ok(id_, expected, given=response.body)
162 finally:
162 finally:
163 fixture.destroy_repo_group(repo_group_name)
163 fixture.destroy_repo_group(repo_group_name)
164
164
165 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
165 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
166 self, user_util):
166 self, user_util):
167
167
168 repo_group_name = 'api-repo-group-parent'
168 repo_group_name = 'api-repo-group-parent'
169
169
170 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
170 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
171 assert repo_group is None
171 assert repo_group is None
172 # create the parent
172 # create the parent
173 fixture.create_repo_group(repo_group_name)
173 fixture.create_repo_group(repo_group_name)
174
174
175 # user perms
175 # user perms
176 regular_user = user_util.create_user()
176 regular_user = user_util.create_user()
177 regular_user_api_key = regular_user.api_key
177 regular_user_api_key = regular_user.api_key
178
178
179 usr = UserModel().get_by_username(regular_user.username)
179 usr = UserModel().get_by_username(regular_user.username)
180 usr.inherit_default_permissions = False
180 usr.inherit_default_permissions = False
181 Session().add(usr)
181 Session().add(usr)
182
182
183 RepoGroupModel().grant_user_permission(
183 RepoGroupModel().grant_user_permission(
184 repo_group_name, regular_user.username, 'group.admin')
184 repo_group_name, regular_user.username, 'group.admin')
185 Session().commit()
185 Session().commit()
186
186
187 full_repo_group_name = repo_group_name + '/' + repo_group_name
187 full_repo_group_name = repo_group_name + '/' + repo_group_name
188 id_, params = build_data(
188 id_, params = build_data(
189 regular_user_api_key, 'create_repo_group',
189 regular_user_api_key, 'create_repo_group',
190 group_name=full_repo_group_name)
190 group_name=full_repo_group_name)
191 response = api_call(self.app, params)
191 response = api_call(self.app, params)
192
192
193 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
193 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
194 assert repo_group is not None
194 assert repo_group is not None
195 expected = {
195 expected = {
196 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
196 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
197 'repo_group': repo_group.get_api_data()
197 'repo_group': repo_group.get_api_data()
198 }
198 }
199 try:
199 try:
200 assert_ok(id_, expected, given=response.body)
200 assert_ok(id_, expected, given=response.body)
201 finally:
201 finally:
202 fixture.destroy_repo_group(full_repo_group_name)
202 fixture.destroy_repo_group(full_repo_group_name)
203 fixture.destroy_repo_group(repo_group_name)
203 fixture.destroy_repo_group(repo_group_name)
204
204
205 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
205 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
206 repo_group_name = 'api-repo-group'
206 repo_group_name = 'api-repo-group'
207
207
208 id_, params = build_data(
208 id_, params = build_data(
209 self.apikey_regular, 'create_repo_group',
209 self.apikey_regular, 'create_repo_group',
210 group_name=repo_group_name)
210 group_name=repo_group_name)
211 response = api_call(self.app, params)
211 response = api_call(self.app, params)
212
212
213 expected = {
213 expected = {
214 'repo_group':
214 'repo_group':
215 u'You do not have the permission to store '
215 u'You do not have the permission to store '
216 u'repository groups in the root location.'}
216 u'repository groups in the root location.'}
217 assert_error(id_, expected, given=response.body)
217 assert_error(id_, expected, given=response.body)
218
218
219 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
219 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
220 repo_group_name = 'api-repo-group-regular-user'
220 repo_group_name = 'api-repo-group-regular-user'
221
221
222 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
222 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
223 assert repo_group is None
223 assert repo_group is None
224 # create the parent
224 # create the parent
225 fixture.create_repo_group(repo_group_name)
225 fixture.create_repo_group(repo_group_name)
226
226
227 full_repo_group_name = repo_group_name+'/'+repo_group_name
227 full_repo_group_name = repo_group_name+'/'+repo_group_name
228
228
229 id_, params = build_data(
229 id_, params = build_data(
230 self.apikey_regular, 'create_repo_group',
230 self.apikey_regular, 'create_repo_group',
231 group_name=full_repo_group_name)
231 group_name=full_repo_group_name)
232 response = api_call(self.app, params)
232 response = api_call(self.app, params)
233
233
234 expected = {
234 expected = {
235 'repo_group':
235 'repo_group':
236 'Parent repository group `{}` does not exist'.format(
236 'Parent repository group `{}` does not exist'.format(
237 repo_group_name)}
237 repo_group_name)}
238 try:
238 try:
239 assert_error(id_, expected, given=response.body)
239 assert_error(id_, expected, given=response.body)
240 finally:
240 finally:
241 fixture.destroy_repo_group(repo_group_name)
241 fixture.destroy_repo_group(repo_group_name)
242
242
243 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
243 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
244 self):
244 self):
245 repo_group_name = 'api-repo-group'
245 repo_group_name = 'api-repo-group'
246
246
247 id_, params = build_data(
247 id_, params = build_data(
248 self.apikey_regular, 'create_repo_group',
248 self.apikey_regular, 'create_repo_group',
249 group_name=repo_group_name,
249 group_name=repo_group_name,
250 owner=TEST_USER_ADMIN_LOGIN,)
250 owner=TEST_USER_ADMIN_LOGIN,)
251 response = api_call(self.app, params)
251 response = api_call(self.app, params)
252
252
253 expected = "Only RhodeCode super-admin can specify `owner` param"
253 expected = "Only RhodeCode super-admin can specify `owner` param"
254 assert_error(id_, expected, given=response.body)
254 assert_error(id_, expected, given=response.body)
255
255
256 @mock.patch.object(RepoGroupModel, 'create', crash)
256 @mock.patch.object(RepoGroupModel, 'create', crash)
257 def test_api_create_repo_group_exception_occurred(self):
257 def test_api_create_repo_group_exception_occurred(self):
258 repo_group_name = 'api-repo-group'
258 repo_group_name = 'api-repo-group'
259
259
260 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
260 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
261 assert repo_group is None
261 assert repo_group is None
262
262
263 id_, params = build_data(
263 id_, params = build_data(
264 self.apikey, 'create_repo_group',
264 self.apikey, 'create_repo_group',
265 group_name=repo_group_name,
265 group_name=repo_group_name,
266 owner=TEST_USER_ADMIN_LOGIN,)
266 owner=TEST_USER_ADMIN_LOGIN,)
267 response = api_call(self.app, params)
267 response = api_call(self.app, params)
268 expected = 'failed to create repo group `%s`' % (repo_group_name,)
268 expected = 'failed to create repo group `%s`' % (repo_group_name,)
269 assert_error(id_, expected, given=response.body)
269 assert_error(id_, expected, given=response.body)
270
270
271 def test_create_group_with_extra_slashes_in_name(self, user_util):
271 def test_create_group_with_extra_slashes_in_name(self, user_util):
272 existing_repo_group = user_util.create_repo_group()
272 existing_repo_group = user_util.create_repo_group()
273 dirty_group_name = '//{}//group2//'.format(
273 dirty_group_name = '//{}//group2//'.format(
274 existing_repo_group.group_name)
274 existing_repo_group.group_name)
275 cleaned_group_name = '{}/group2'.format(
275 cleaned_group_name = '{}/group2'.format(
276 existing_repo_group.group_name)
276 existing_repo_group.group_name)
277
277
278 id_, params = build_data(
278 id_, params = build_data(
279 self.apikey, 'create_repo_group',
279 self.apikey, 'create_repo_group',
280 group_name=dirty_group_name,
280 group_name=dirty_group_name,
281 owner=TEST_USER_ADMIN_LOGIN,)
281 owner=TEST_USER_ADMIN_LOGIN,)
282 response = api_call(self.app, params)
282 response = api_call(self.app, params)
283 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
283 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
284 expected = {
284 expected = {
285 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
285 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
286 'repo_group': repo_group.get_api_data()
286 'repo_group': repo_group.get_api_data()
287 }
287 }
288 assert_ok(id_, expected, given=response.body)
288 assert_ok(id_, expected, given=response.body)
289 fixture.destroy_repo_group(cleaned_group_name)
289 fixture.destroy_repo_group(cleaned_group_name)
@@ -1,206 +1,206 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.auth import check_password
24 from rhodecode.lib.auth import check_password
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import (
26 from rhodecode.tests import (
27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.model.db import RepoGroup
31 from rhodecode.model.db import RepoGroup
32
32
33
33
34 # TODO: mikhail: remove fixture from here
34 # TODO: mikhail: remove fixture from here
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37
37
38 @pytest.mark.usefixtures("testuser_api", "app")
38 @pytest.mark.usefixtures("testuser_api", "app")
39 class TestCreateUser(object):
39 class TestCreateUser(object):
40 def test_api_create_existing_user(self):
40 def test_api_create_existing_user(self):
41 id_, params = build_data(
41 id_, params = build_data(
42 self.apikey, 'create_user',
42 self.apikey, 'create_user',
43 username=TEST_USER_ADMIN_LOGIN,
43 username=TEST_USER_ADMIN_LOGIN,
44 email='test@foo.com',
44 email='test@foo.com',
45 password='trololo')
45 password='trololo')
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
49 assert_error(id_, expected, given=response.body)
49 assert_error(id_, expected, given=response.body)
50
50
51 def test_api_create_user_with_existing_email(self):
51 def test_api_create_user_with_existing_email(self):
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'create_user',
53 self.apikey, 'create_user',
54 username=TEST_USER_ADMIN_LOGIN + 'new',
54 username=TEST_USER_ADMIN_LOGIN + 'new',
55 email=TEST_USER_REGULAR_EMAIL,
55 email=TEST_USER_REGULAR_EMAIL,
56 password='trololo')
56 password='trololo')
57 response = api_call(self.app, params)
57 response = api_call(self.app, params)
58
58
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
60 assert_error(id_, expected, given=response.body)
60 assert_error(id_, expected, given=response.body)
61
61
62 def test_api_create_user_with_wrong_username(self):
62 def test_api_create_user_with_wrong_username(self):
63 bad_username = '<> HELLO WORLD <>'
63 bad_username = '<> HELLO WORLD <>'
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'create_user',
65 self.apikey, 'create_user',
66 username=bad_username,
66 username=bad_username,
67 email='new@email.com',
67 email='new@email.com',
68 password='trololo')
68 password='trololo')
69 response = api_call(self.app, params)
69 response = api_call(self.app, params)
70
70
71 expected = {'username':
71 expected = {'username':
72 "Username may only contain alphanumeric characters "
72 "Username may only contain alphanumeric characters "
73 "underscores, periods or dashes and must begin with "
73 "underscores, periods or dashes and must begin with "
74 "alphanumeric character or underscore"}
74 "alphanumeric character or underscore"}
75 assert_error(id_, expected, given=response.body)
75 assert_error(id_, expected, given=response.body)
76
76
77 def test_api_create_user(self):
77 def test_api_create_user(self):
78 username = 'test_new_api_user'
78 username = 'test_new_api_user'
79 email = username + "@foo.com"
79 email = username + "@foo.com"
80
80
81 id_, params = build_data(
81 id_, params = build_data(
82 self.apikey, 'create_user',
82 self.apikey, 'create_user',
83 username=username,
83 username=username,
84 email=email,
84 email=email,
85 password='example')
85 password='example')
86 response = api_call(self.app, params)
86 response = api_call(self.app, params)
87
87
88 usr = UserModel().get_by_username(username)
88 usr = UserModel().get_by_username(username)
89 ret = {
89 ret = {
90 'msg': 'created new user `%s`' % (username,),
90 'msg': 'created new user `%s`' % (username,),
91 'user': jsonify(usr.get_api_data(include_secrets=True)),
91 'user': jsonify(usr.get_api_data(include_secrets=True)),
92 }
92 }
93 try:
93 try:
94 expected = ret
94 expected = ret
95 assert check_password('example', usr.password)
95 assert check_password('example', usr.password)
96 assert_ok(id_, expected, given=response.body)
96 assert_ok(id_, expected, given=response.body)
97 finally:
97 finally:
98 fixture.destroy_user(usr.user_id)
98 fixture.destroy_user(usr.user_id)
99
99
100 def test_api_create_user_without_password(self):
100 def test_api_create_user_without_password(self):
101 username = 'test_new_api_user_passwordless'
101 username = 'test_new_api_user_passwordless'
102 email = username + "@foo.com"
102 email = username + "@foo.com"
103
103
104 id_, params = build_data(
104 id_, params = build_data(
105 self.apikey, 'create_user',
105 self.apikey, 'create_user',
106 username=username,
106 username=username,
107 email=email)
107 email=email)
108 response = api_call(self.app, params)
108 response = api_call(self.app, params)
109
109
110 usr = UserModel().get_by_username(username)
110 usr = UserModel().get_by_username(username)
111 ret = {
111 ret = {
112 'msg': 'created new user `%s`' % (username,),
112 'msg': 'created new user `%s`' % (username,),
113 'user': jsonify(usr.get_api_data(include_secrets=True)),
113 'user': jsonify(usr.get_api_data(include_secrets=True)),
114 }
114 }
115 try:
115 try:
116 expected = ret
116 expected = ret
117 assert_ok(id_, expected, given=response.body)
117 assert_ok(id_, expected, given=response.body)
118 finally:
118 finally:
119 fixture.destroy_user(usr.user_id)
119 fixture.destroy_user(usr.user_id)
120
120
121 def test_api_create_user_with_extern_name(self):
121 def test_api_create_user_with_extern_name(self):
122 username = 'test_new_api_user_passwordless'
122 username = 'test_new_api_user_passwordless'
123 email = username + "@foo.com"
123 email = username + "@foo.com"
124
124
125 id_, params = build_data(
125 id_, params = build_data(
126 self.apikey, 'create_user',
126 self.apikey, 'create_user',
127 username=username,
127 username=username,
128 email=email, extern_name='rhodecode')
128 email=email, extern_name='rhodecode')
129 response = api_call(self.app, params)
129 response = api_call(self.app, params)
130
130
131 usr = UserModel().get_by_username(username)
131 usr = UserModel().get_by_username(username)
132 ret = {
132 ret = {
133 'msg': 'created new user `%s`' % (username,),
133 'msg': 'created new user `%s`' % (username,),
134 'user': jsonify(usr.get_api_data(include_secrets=True)),
134 'user': jsonify(usr.get_api_data(include_secrets=True)),
135 }
135 }
136 try:
136 try:
137 expected = ret
137 expected = ret
138 assert_ok(id_, expected, given=response.body)
138 assert_ok(id_, expected, given=response.body)
139 finally:
139 finally:
140 fixture.destroy_user(usr.user_id)
140 fixture.destroy_user(usr.user_id)
141
141
142 def test_api_create_user_with_password_change(self):
142 def test_api_create_user_with_password_change(self):
143 username = 'test_new_api_user_password_change'
143 username = 'test_new_api_user_password_change'
144 email = username + "@foo.com"
144 email = username + "@foo.com"
145
145
146 id_, params = build_data(
146 id_, params = build_data(
147 self.apikey, 'create_user',
147 self.apikey, 'create_user',
148 username=username,
148 username=username,
149 email=email, extern_name='rhodecode',
149 email=email, extern_name='rhodecode',
150 force_password_change=True)
150 force_password_change=True)
151 response = api_call(self.app, params)
151 response = api_call(self.app, params)
152
152
153 usr = UserModel().get_by_username(username)
153 usr = UserModel().get_by_username(username)
154 ret = {
154 ret = {
155 'msg': 'created new user `%s`' % (username,),
155 'msg': 'created new user `%s`' % (username,),
156 'user': jsonify(usr.get_api_data(include_secrets=True)),
156 'user': jsonify(usr.get_api_data(include_secrets=True)),
157 }
157 }
158 try:
158 try:
159 expected = ret
159 expected = ret
160 assert_ok(id_, expected, given=response.body)
160 assert_ok(id_, expected, given=response.body)
161 finally:
161 finally:
162 fixture.destroy_user(usr.user_id)
162 fixture.destroy_user(usr.user_id)
163
163
164 def test_api_create_user_with_personal_repo_group(self):
164 def test_api_create_user_with_personal_repo_group(self):
165 username = 'test_new_api_user_personal_group'
165 username = 'test_new_api_user_personal_group'
166 email = username + "@foo.com"
166 email = username + "@foo.com"
167
167
168 id_, params = build_data(
168 id_, params = build_data(
169 self.apikey, 'create_user',
169 self.apikey, 'create_user',
170 username=username,
170 username=username,
171 email=email, extern_name='rhodecode',
171 email=email, extern_name='rhodecode',
172 create_personal_repo_group=True)
172 create_personal_repo_group=True)
173 response = api_call(self.app, params)
173 response = api_call(self.app, params)
174
174
175 usr = UserModel().get_by_username(username)
175 usr = UserModel().get_by_username(username)
176 ret = {
176 ret = {
177 'msg': 'created new user `%s`' % (username,),
177 'msg': 'created new user `%s`' % (username,),
178 'user': jsonify(usr.get_api_data(include_secrets=True)),
178 'user': jsonify(usr.get_api_data(include_secrets=True)),
179 }
179 }
180
180
181 personal_group = RepoGroup.get_by_group_name(username)
181 personal_group = RepoGroup.get_by_group_name(username)
182 assert personal_group
182 assert personal_group
183 assert personal_group.personal == True
183 assert personal_group.personal == True
184 assert personal_group.user.username == username
184 assert personal_group.user.username == username
185
185
186 try:
186 try:
187 expected = ret
187 expected = ret
188 assert_ok(id_, expected, given=response.body)
188 assert_ok(id_, expected, given=response.body)
189 finally:
189 finally:
190 fixture.destroy_repo_group(username)
190 fixture.destroy_repo_group(username)
191 fixture.destroy_user(usr.user_id)
191 fixture.destroy_user(usr.user_id)
192
192
193 @mock.patch.object(UserModel, 'create_or_update', crash)
193 @mock.patch.object(UserModel, 'create_or_update', crash)
194 def test_api_create_user_when_exception_happened(self):
194 def test_api_create_user_when_exception_happened(self):
195
195
196 username = 'test_new_api_user'
196 username = 'test_new_api_user'
197 email = username + "@foo.com"
197 email = username + "@foo.com"
198
198
199 id_, params = build_data(
199 id_, params = build_data(
200 self.apikey, 'create_user',
200 self.apikey, 'create_user',
201 username=username,
201 username=username,
202 email=email,
202 email=email,
203 password='trololo')
203 password='trololo')
204 response = api_call(self.app, params)
204 response = api_call(self.app, params)
205 expected = 'failed to create user `%s`' % (username,)
205 expected = 'failed to create user `%s`' % (username,)
206 assert_error(id_, expected, given=response.body)
206 assert_error(id_, expected, given=response.body)
@@ -1,127 +1,127 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.model.user_group import UserGroupModel
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30
30
31
31
32 @pytest.mark.usefixtures("testuser_api", "app")
32 @pytest.mark.usefixtures("testuser_api", "app")
33 class TestCreateUserGroup(object):
33 class TestCreateUserGroup(object):
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36 def test_api_create_user_group(self):
36 def test_api_create_user_group(self):
37 group_name = 'some_new_group'
37 group_name = 'some_new_group'
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey, 'create_user_group', group_name=group_name)
39 self.apikey, 'create_user_group', group_name=group_name)
40 response = api_call(self.app, params)
40 response = api_call(self.app, params)
41
41
42 ret = {
42 ret = {
43 'msg': 'created new user group `%s`' % (group_name,),
43 'msg': 'created new user group `%s`' % (group_name,),
44 'user_group': jsonify(
44 'user_group': jsonify(
45 UserGroupModel()
45 UserGroupModel()
46 .get_by_name(group_name)
46 .get_by_name(group_name)
47 .get_api_data()
47 .get_api_data()
48 )
48 )
49 }
49 }
50 expected = ret
50 expected = ret
51 assert_ok(id_, expected, given=response.body)
51 assert_ok(id_, expected, given=response.body)
52 self.fixture.destroy_user_group(group_name)
52 self.fixture.destroy_user_group(group_name)
53
53
54 def test_api_create_user_group_regular_user(self):
54 def test_api_create_user_group_regular_user(self):
55 group_name = 'some_new_group'
55 group_name = 'some_new_group'
56
56
57 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
58 usr.inherit_default_permissions = False
58 usr.inherit_default_permissions = False
59 Session().add(usr)
59 Session().add(usr)
60 UserModel().grant_perm(
60 UserModel().grant_perm(
61 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
61 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
62 Session().commit()
62 Session().commit()
63
63
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey_regular, 'create_user_group', group_name=group_name)
65 self.apikey_regular, 'create_user_group', group_name=group_name)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = {
68 expected = {
69 'msg': 'created new user group `%s`' % (group_name,),
69 'msg': 'created new user group `%s`' % (group_name,),
70 'user_group': jsonify(
70 'user_group': jsonify(
71 UserGroupModel()
71 UserGroupModel()
72 .get_by_name(group_name)
72 .get_by_name(group_name)
73 .get_api_data()
73 .get_api_data()
74 )
74 )
75 }
75 }
76 try:
76 try:
77 assert_ok(id_, expected, given=response.body)
77 assert_ok(id_, expected, given=response.body)
78 finally:
78 finally:
79 self.fixture.destroy_user_group(group_name)
79 self.fixture.destroy_user_group(group_name)
80 UserModel().revoke_perm(
80 UserModel().revoke_perm(
81 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
81 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
82 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
82 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
83 usr.inherit_default_permissions = True
83 usr.inherit_default_permissions = True
84 Session().add(usr)
84 Session().add(usr)
85 Session().commit()
85 Session().commit()
86
86
87 def test_api_create_user_group_regular_user_no_permission(self):
87 def test_api_create_user_group_regular_user_no_permission(self):
88 group_name = 'some_new_group'
88 group_name = 'some_new_group'
89 id_, params = build_data(
89 id_, params = build_data(
90 self.apikey_regular, 'create_user_group', group_name=group_name)
90 self.apikey_regular, 'create_user_group', group_name=group_name)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92 expected = "Access was denied to this resource."
92 expected = "Access was denied to this resource."
93 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
94
94
95 def test_api_create_user_group_that_exist(self, user_util):
95 def test_api_create_user_group_that_exist(self, user_util):
96 group = user_util.create_user_group()
96 group = user_util.create_user_group()
97 group_name = group.users_group_name
97 group_name = group.users_group_name
98
98
99 id_, params = build_data(
99 id_, params = build_data(
100 self.apikey, 'create_user_group', group_name=group_name)
100 self.apikey, 'create_user_group', group_name=group_name)
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102
102
103 expected = "user group `%s` already exist" % (group_name,)
103 expected = "user group `%s` already exist" % (group_name,)
104 assert_error(id_, expected, given=response.body)
104 assert_error(id_, expected, given=response.body)
105
105
106 @mock.patch.object(UserGroupModel, 'create', crash)
106 @mock.patch.object(UserGroupModel, 'create', crash)
107 def test_api_create_user_group_exception_occurred(self):
107 def test_api_create_user_group_exception_occurred(self):
108 group_name = 'exception_happens'
108 group_name = 'exception_happens'
109 id_, params = build_data(
109 id_, params = build_data(
110 self.apikey, 'create_user_group', group_name=group_name)
110 self.apikey, 'create_user_group', group_name=group_name)
111 response = api_call(self.app, params)
111 response = api_call(self.app, params)
112
112
113 expected = 'failed to create group `%s`' % (group_name,)
113 expected = 'failed to create group `%s`' % (group_name,)
114 assert_error(id_, expected, given=response.body)
114 assert_error(id_, expected, given=response.body)
115
115
116 def test_api_create_user_group_with_wrong_name(self, user_util):
116 def test_api_create_user_group_with_wrong_name(self, user_util):
117
117
118 group_name = 'wrong NAME <>'
118 group_name = 'wrong NAME <>'
119 id_, params = build_data(
119 id_, params = build_data(
120 self.apikey, 'create_user_group', group_name=group_name)
120 self.apikey, 'create_user_group', group_name=group_name)
121 response = api_call(self.app, params)
121 response = api_call(self.app, params)
122
122
123 expected = {"user_group_name":
123 expected = {"user_group_name":
124 "Allowed in name are letters, numbers, and `-`, `_`, "
124 "Allowed in name are letters, numbers, and `-`, `_`, "
125 "`.` Name must start with a letter or number. "
125 "`.` Name must start with a letter or number. "
126 "Got `{}`".format(group_name)}
126 "Got `{}`".format(group_name)}
127 assert_error(id_, expected, given=response.body)
127 assert_error(id_, expected, given=response.body)
@@ -1,61 +1,61 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.gist import GistModel
24 from rhodecode.model.gist import GistModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiDeleteGist(object):
30 class TestApiDeleteGist(object):
31 def test_api_delete_gist(self, gist_util):
31 def test_api_delete_gist(self, gist_util):
32 gist_id = gist_util.create_gist().gist_access_id
32 gist_id = gist_util.create_gist().gist_access_id
33 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
33 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
35 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
37
37
38 def test_api_delete_gist_regular_user(self, gist_util):
38 def test_api_delete_gist_regular_user(self, gist_util):
39 gist_id = gist_util.create_gist(
39 gist_id = gist_util.create_gist(
40 owner=self.TEST_USER_LOGIN).gist_access_id
40 owner=self.TEST_USER_LOGIN).gist_access_id
41 id_, params = build_data(
41 id_, params = build_data(
42 self.apikey_regular, 'delete_gist', gistid=gist_id)
42 self.apikey_regular, 'delete_gist', gistid=gist_id)
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
44 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
45 assert_ok(id_, expected, given=response.body)
45 assert_ok(id_, expected, given=response.body)
46
46
47 def test_api_delete_gist_regular_user_no_permission(self, gist_util):
47 def test_api_delete_gist_regular_user_no_permission(self, gist_util):
48 gist_id = gist_util.create_gist().gist_access_id
48 gist_id = gist_util.create_gist().gist_access_id
49 id_, params = build_data(
49 id_, params = build_data(
50 self.apikey_regular, 'delete_gist', gistid=gist_id)
50 self.apikey_regular, 'delete_gist', gistid=gist_id)
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52 expected = 'gist `%s` does not exist' % (gist_id,)
52 expected = 'gist `%s` does not exist' % (gist_id,)
53 assert_error(id_, expected, given=response.body)
53 assert_error(id_, expected, given=response.body)
54
54
55 @mock.patch.object(GistModel, 'delete', crash)
55 @mock.patch.object(GistModel, 'delete', crash)
56 def test_api_delete_gist_exception_occurred(self, gist_util):
56 def test_api_delete_gist_exception_occurred(self, gist_util):
57 gist_id = gist_util.create_gist().gist_access_id
57 gist_id = gist_util.create_gist().gist_access_id
58 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
58 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60 expected = 'failed to delete gist ID:%s' % (gist_id,)
60 expected = 'failed to delete gist ID:%s' % (gist_id,)
61 assert_error(id_, expected, given=response.body)
61 assert_error(id_, expected, given=response.body)
@@ -1,74 +1,74 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiDeleteRepo(object):
30 class TestApiDeleteRepo(object):
31 def test_api_delete_repo(self, backend):
31 def test_api_delete_repo(self, backend):
32 repo = backend.create_repo()
32 repo = backend.create_repo()
33 repo_name = repo.repo_name
33 repo_name = repo.repo_name
34 id_, params = build_data(
34 id_, params = build_data(
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
36 response = api_call(self.app, params)
36 response = api_call(self.app, params)
37
37
38 expected = {
38 expected = {
39 'msg': 'Deleted repository `%s`' % (repo_name,),
39 'msg': 'Deleted repository `%s`' % (repo_name,),
40 'success': True
40 'success': True
41 }
41 }
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
45 repo = backend.create_repo(cur_user=user_regular.username)
45 repo = backend.create_repo(cur_user=user_regular.username)
46 repo_name = repo.repo_name
46 repo_name = repo.repo_name
47 id_, params = build_data(
47 id_, params = build_data(
48 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
50
51 expected = {
51 expected = {
52 'msg': 'Deleted repository `%s`' % (repo_name,),
52 'msg': 'Deleted repository `%s`' % (repo_name,),
53 'success': True
53 'success': True
54 }
54 }
55 assert_ok(id_, expected, given=response.body)
55 assert_ok(id_, expected, given=response.body)
56
56
57 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
58 repo = backend.create_repo()
58 repo = backend.create_repo()
59 repo_name = repo.repo_name
59 repo_name = repo.repo_name
60 id_, params = build_data(
60 id_, params = build_data(
61 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
61 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
62 response = api_call(self.app, params)
62 response = api_call(self.app, params)
63 expected = 'repository `%s` does not exist' % (repo_name)
63 expected = 'repository `%s` does not exist' % (repo_name)
64 assert_error(id_, expected, given=response.body)
64 assert_error(id_, expected, given=response.body)
65
65
66 def test_api_delete_repo_exception_occurred(self, backend):
66 def test_api_delete_repo_exception_occurred(self, backend):
67 repo = backend.create_repo()
67 repo = backend.create_repo()
68 repo_name = repo.repo_name
68 repo_name = repo.repo_name
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'delete_repo', repoid=repo.repo_name, )
70 self.apikey, 'delete_repo', repoid=repo.repo_name, )
71 with mock.patch.object(RepoModel, 'delete', crash):
71 with mock.patch.object(RepoModel, 'delete', crash):
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73 expected = 'failed to delete repository `%s`' % (repo_name,)
73 expected = 'failed to delete repository `%s`' % (repo_name,)
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
@@ -1,86 +1,86 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestApiDeleteRepoGroup(object):
31 class TestApiDeleteRepoGroup(object):
32 def test_api_delete_repo_group(self, user_util):
32 def test_api_delete_repo_group(self, user_util):
33 repo_group = user_util.create_repo_group(auto_cleanup=False)
33 repo_group = user_util.create_repo_group(auto_cleanup=False)
34 repo_group_name = repo_group.group_name
34 repo_group_name = repo_group.group_name
35 repo_group_id = repo_group.group_id
35 repo_group_id = repo_group.group_id
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
37 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 ret = {
40 ret = {
41 'msg': 'deleted repo group ID:%s %s' % (
41 'msg': 'deleted repo group ID:%s %s' % (
42 repo_group_id, repo_group_name
42 repo_group_id, repo_group_name
43 ),
43 ),
44 'repo_group': None
44 'repo_group': None
45 }
45 }
46 expected = ret
46 expected = ret
47 assert_ok(id_, expected, given=response.body)
47 assert_ok(id_, expected, given=response.body)
48 gr = RepoGroupModel()._get_repo_group(repo_group_name)
48 gr = RepoGroupModel()._get_repo_group(repo_group_name)
49 assert gr is None
49 assert gr is None
50
50
51 def test_api_delete_repo_group_regular_user(self, user_util):
51 def test_api_delete_repo_group_regular_user(self, user_util):
52 repo_group = user_util.create_repo_group(auto_cleanup=False)
52 repo_group = user_util.create_repo_group(auto_cleanup=False)
53 repo_group_name = repo_group.group_name
53 repo_group_name = repo_group.group_name
54 repo_group_id = repo_group.group_id
54 repo_group_id = repo_group.group_id
55
55
56 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
56 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 user_util.grant_user_permission_to_repo_group(
57 user_util.grant_user_permission_to_repo_group(
58 repo_group, user, 'group.admin')
58 repo_group, user, 'group.admin')
59
59
60 id_, params = build_data(
60 id_, params = build_data(
61 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
61 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
62 response = api_call(self.app, params)
62 response = api_call(self.app, params)
63
63
64 ret = {
64 ret = {
65 'msg': 'deleted repo group ID:%s %s' % (
65 'msg': 'deleted repo group ID:%s %s' % (
66 repo_group_id, repo_group_name
66 repo_group_id, repo_group_name
67 ),
67 ),
68 'repo_group': None
68 'repo_group': None
69 }
69 }
70 expected = ret
70 expected = ret
71 assert_ok(id_, expected, given=response.body)
71 assert_ok(id_, expected, given=response.body)
72 gr = RepoGroupModel()._get_repo_group(repo_group_name)
72 gr = RepoGroupModel()._get_repo_group(repo_group_name)
73 assert gr is None
73 assert gr is None
74
74
75 def test_api_delete_repo_group_regular_user_no_permission(self, user_util):
75 def test_api_delete_repo_group_regular_user_no_permission(self, user_util):
76 repo_group = user_util.create_repo_group()
76 repo_group = user_util.create_repo_group()
77 repo_group_name = repo_group.group_name
77 repo_group_name = repo_group.group_name
78
78
79 id_, params = build_data(
79 id_, params = build_data(
80 self.apikey_regular, 'delete_repo_group',
80 self.apikey_regular, 'delete_repo_group',
81 repogroupid=repo_group_name, )
81 repogroupid=repo_group_name, )
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83
83
84 expected = 'repository group `%s` does not exist' % (
84 expected = 'repository group `%s` does not exist' % (
85 repo_group_name,)
85 repo_group_name,)
86 assert_error(id_, expected, given=response.body)
86 assert_error(id_, expected, given=response.body)
@@ -1,57 +1,57 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error, crash)
27 build_data, api_call, assert_ok, assert_error, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestDeleteUser(object):
31 class TestDeleteUser(object):
32 def test_api_delete_user(self, user_util):
32 def test_api_delete_user(self, user_util):
33 usr = user_util.create_user(auto_cleanup=False)
33 usr = user_util.create_user(auto_cleanup=False)
34
34
35 username = usr.username
35 username = usr.username
36 usr_id = usr.user_id
36 usr_id = usr.user_id
37
37
38 id_, params = build_data(self.apikey, 'delete_user', userid=username)
38 id_, params = build_data(self.apikey, 'delete_user', userid=username)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40
40
41 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
41 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
42 'user': None}
42 'user': None}
43 expected = ret
43 expected = ret
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 @mock.patch.object(UserModel, 'delete', crash)
46 @mock.patch.object(UserModel, 'delete', crash)
47 def test_api_delete_user_when_exception_happened(self, user_util):
47 def test_api_delete_user_when_exception_happened(self, user_util):
48 usr = user_util.create_user()
48 usr = user_util.create_user()
49 username = usr.username
49 username = usr.username
50
50
51 id_, params = build_data(
51 id_, params = build_data(
52 self.apikey, 'delete_user', userid=username, )
52 self.apikey, 'delete_user', userid=username, )
53 response = api_call(self.app, params)
53 response = api_call(self.app, params)
54 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
54 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
55 usr.username)
55 usr.username)
56 expected = ret
56 expected = ret
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
@@ -1,106 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.model.user_group import UserGroupModel
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash)
28 build_data, api_call, assert_error, assert_ok, crash)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestDeleteUserGroup(object):
32 class TestDeleteUserGroup(object):
33 def test_api_delete_user_group(self, user_util):
33 def test_api_delete_user_group(self, user_util):
34 user_group = user_util.create_user_group(auto_cleanup=False)
34 user_group = user_util.create_user_group(auto_cleanup=False)
35 group_name = user_group.users_group_name
35 group_name = user_group.users_group_name
36 group_id = user_group.users_group_id
36 group_id = user_group.users_group_id
37 id_, params = build_data(
37 id_, params = build_data(
38 self.apikey, 'delete_user_group', usergroupid=group_name)
38 self.apikey, 'delete_user_group', usergroupid=group_name)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40
40
41 expected = {
41 expected = {
42 'user_group': None,
42 'user_group': None,
43 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
43 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
44 }
44 }
45 assert_ok(id_, expected, given=response.body)
45 assert_ok(id_, expected, given=response.body)
46
46
47 def test_api_delete_user_group_regular_user(self, user_util):
47 def test_api_delete_user_group_regular_user(self, user_util):
48 ugroup = user_util.create_user_group(auto_cleanup=False)
48 ugroup = user_util.create_user_group(auto_cleanup=False)
49 group_name = ugroup.users_group_name
49 group_name = ugroup.users_group_name
50 group_id = ugroup.users_group_id
50 group_id = ugroup.users_group_id
51 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
51 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
52
52
53 user_util.grant_user_permission_to_user_group(
53 user_util.grant_user_permission_to_user_group(
54 ugroup, user, 'usergroup.admin')
54 ugroup, user, 'usergroup.admin')
55
55
56 id_, params = build_data(
56 id_, params = build_data(
57 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
57 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
58 response = api_call(self.app, params)
58 response = api_call(self.app, params)
59
59
60 expected = {
60 expected = {
61 'user_group': None,
61 'user_group': None,
62 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
62 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
63 }
63 }
64 assert_ok(id_, expected, given=response.body)
64 assert_ok(id_, expected, given=response.body)
65
65
66 def test_api_delete_user_group_regular_user_no_permission(self, user_util):
66 def test_api_delete_user_group_regular_user_no_permission(self, user_util):
67 user_group = user_util.create_user_group()
67 user_group = user_util.create_user_group()
68 group_name = user_group.users_group_name
68 group_name = user_group.users_group_name
69
69
70 id_, params = build_data(
70 id_, params = build_data(
71 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
71 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73
73
74 expected = 'user group `%s` does not exist' % (group_name)
74 expected = 'user group `%s` does not exist' % (group_name)
75 assert_error(id_, expected, given=response.body)
75 assert_error(id_, expected, given=response.body)
76
76
77 def test_api_delete_user_group_that_is_assigned(self, backend, user_util):
77 def test_api_delete_user_group_that_is_assigned(self, backend, user_util):
78 ugroup = user_util.create_user_group()
78 ugroup = user_util.create_user_group()
79 group_name = ugroup.users_group_name
79 group_name = ugroup.users_group_name
80 repo = backend.create_repo()
80 repo = backend.create_repo()
81
81
82 ugr_to_perm = user_util.grant_user_group_permission_to_repo(
82 ugr_to_perm = user_util.grant_user_group_permission_to_repo(
83 repo, ugroup, 'repository.write')
83 repo, ugroup, 'repository.write')
84 msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository)
84 msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository)
85
85
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey, 'delete_user_group',
87 self.apikey, 'delete_user_group',
88 usergroupid=group_name)
88 usergroupid=group_name)
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90
90
91 expected = msg
91 expected = msg
92 assert_error(id_, expected, given=response.body)
92 assert_error(id_, expected, given=response.body)
93
93
94 def test_api_delete_user_group_exception_occurred(self, user_util):
94 def test_api_delete_user_group_exception_occurred(self, user_util):
95 ugroup = user_util.create_user_group()
95 ugroup = user_util.create_user_group()
96 group_name = ugroup.users_group_name
96 group_name = ugroup.users_group_name
97 group_id = ugroup.users_group_id
97 group_id = ugroup.users_group_id
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'delete_user_group',
99 self.apikey, 'delete_user_group',
100 usergroupid=group_name)
100 usergroupid=group_name)
101
101
102 with mock.patch.object(UserGroupModel, 'delete', crash):
102 with mock.patch.object(UserGroupModel, 'delete', crash):
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104 expected = 'failed to delete user group ID:%s %s' % (
104 expected = 'failed to delete user group ID:%s %s' % (
105 group_id, group_name)
105 group_id, group_name)
106 assert_error(id_, expected, given=response.body)
106 assert_error(id_, expected, given=response.body)
@@ -1,79 +1,79 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.views import deprecated_api
24 from rhodecode.api.views import deprecated_api
25 from rhodecode.lib.ext_json import json
25 from rhodecode.lib.ext_json import json
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call)
27 build_data, api_call)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestCommitComment(object):
31 class TestCommitComment(object):
32 def test_deprecated_message_in_docstring(self):
32 def test_deprecated_message_in_docstring(self):
33 docstring = deprecated_api.changeset_comment.__doc__
33 docstring = deprecated_api.changeset_comment.__doc__
34 assert '.. deprecated:: 3.4.0' in docstring
34 assert '.. deprecated:: 3.4.0' in docstring
35 assert 'Please use method `comment_commit` instead.' in docstring
35 assert 'Please use method `comment_commit` instead.' in docstring
36
36
37 def test_deprecated_message_in_retvalue(self):
37 def test_deprecated_message_in_retvalue(self):
38
38
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'show_ip')
40 self.apikey, 'show_ip')
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = {
43 expected = {
44 'id': id_,
44 'id': id_,
45 'error': None,
45 'error': None,
46 'result': json.loads(response.body)['result'],
46 'result': json.loads(response.body)['result'],
47 'DEPRECATION_WARNING':
47 'DEPRECATION_WARNING':
48 'DEPRECATED METHOD Please use method `get_ip` instead.'
48 'DEPRECATED METHOD Please use method `get_ip` instead.'
49 }
49 }
50 assert expected == json.loads(response.body)
50 assert expected == json.loads(response.body)
51
51
52 # def test_calls_comment_commit(self, backend, no_notifications):
52 # def test_calls_comment_commit(self, backend, no_notifications):
53 # data = {
53 # data = {
54 # 'repoid': backend.repo_name,
54 # 'repoid': backend.repo_name,
55 # 'status': ChangesetStatus.STATUS_APPROVED,
55 # 'status': ChangesetStatus.STATUS_APPROVED,
56 # 'message': 'Approved',
56 # 'message': 'Approved',
57 # 'revision': 'tip'
57 # 'revision': 'tip'
58 # }
58 # }
59 # with patch.object(repo_api, 'changeset_commit') as comment_mock:
59 # with patch.object(repo_api, 'changeset_commit') as comment_mock:
60 # id_, params = build_data(self.apikey, 'comment_commit', **data)
60 # id_, params = build_data(self.apikey, 'comment_commit', **data)
61 # api_call(self.app, params)
61 # api_call(self.app, params)
62 #
62 #
63 # _, call_args = comment_mock.call_args
63 # _, call_args = comment_mock.call_args
64 # data['commit_id'] = data.pop('revision')
64 # data['commit_id'] = data.pop('revision')
65 # for key in data:
65 # for key in data:
66 # assert call_args[key] == data[key]
66 # assert call_args[key] == data[key]
67
67
68 # def test_warning_log_contains_deprecation_message(self):
68 # def test_warning_log_contains_deprecation_message(self):
69 # api = self.SampleApi()
69 # api = self.SampleApi()
70 # with patch.object(utils, 'log') as log_mock:
70 # with patch.object(utils, 'log') as log_mock:
71 # api.api_method()
71 # api.api_method()
72 #
72 #
73 # assert log_mock.warning.call_count == 1
73 # assert log_mock.warning.call_count == 1
74 # call_args = log_mock.warning.call_args[0]
74 # call_args = log_mock.warning.call_args[0]
75 # assert (
75 # assert (
76 # call_args[0] ==
76 # call_args[0] ==
77 # 'DEPRECATED API CALL on function %s, please use `%s` instead')
77 # 'DEPRECATED API CALL on function %s, please use `%s` instead')
78 # assert call_args[1].__name__ == 'api_method'
78 # assert call_args[1].__name__ == 'api_method'
79 # assert call_args[2] == 'new_method' No newline at end of file
79 # assert call_args[2] == 'new_method'
@@ -1,279 +1,279 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.repo import RepoModel
27 from rhodecode.model.repo_group import RepoGroupModel
27 from rhodecode.model.repo_group import RepoGroupModel
28 from rhodecode.model.user import UserModel
28 from rhodecode.model.user import UserModel
29 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
29 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
30 from rhodecode.api.tests.utils import (
30 from rhodecode.api.tests.utils import (
31 build_data, api_call, assert_error, assert_ok, crash)
31 build_data, api_call, assert_error, assert_ok, crash)
32 from rhodecode.tests.fixture import Fixture
32 from rhodecode.tests.fixture import Fixture
33
33
34
34
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37
37
38 @pytest.mark.usefixtures("testuser_api", "app")
38 @pytest.mark.usefixtures("testuser_api", "app")
39 class TestApiForkRepo(object):
39 class TestApiForkRepo(object):
40 def test_api_fork_repo(self, backend):
40 def test_api_fork_repo(self, backend):
41 source_name = backend['minimal'].repo_name
41 source_name = backend['minimal'].repo_name
42 fork_name = backend.new_repo_name()
42 fork_name = backend.new_repo_name()
43
43
44 id_, params = build_data(
44 id_, params = build_data(
45 self.apikey, 'fork_repo',
45 self.apikey, 'fork_repo',
46 repoid=source_name,
46 repoid=source_name,
47 fork_name=fork_name,
47 fork_name=fork_name,
48 owner=TEST_USER_ADMIN_LOGIN)
48 owner=TEST_USER_ADMIN_LOGIN)
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
50
51 expected = {
51 expected = {
52 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
52 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
53 'success': True,
53 'success': True,
54 'task': None,
54 'task': None,
55 }
55 }
56 try:
56 try:
57 assert_ok(id_, expected, given=response.body)
57 assert_ok(id_, expected, given=response.body)
58 finally:
58 finally:
59 fixture.destroy_repo(fork_name)
59 fixture.destroy_repo(fork_name)
60
60
61 def test_api_fork_repo_into_group(self, backend, user_util):
61 def test_api_fork_repo_into_group(self, backend, user_util):
62 source_name = backend['minimal'].repo_name
62 source_name = backend['minimal'].repo_name
63 repo_group = user_util.create_repo_group()
63 repo_group = user_util.create_repo_group()
64 fork_name = '%s/api-repo-fork' % repo_group.group_name
64 fork_name = '%s/api-repo-fork' % repo_group.group_name
65 id_, params = build_data(
65 id_, params = build_data(
66 self.apikey, 'fork_repo',
66 self.apikey, 'fork_repo',
67 repoid=source_name,
67 repoid=source_name,
68 fork_name=fork_name,
68 fork_name=fork_name,
69 owner=TEST_USER_ADMIN_LOGIN)
69 owner=TEST_USER_ADMIN_LOGIN)
70 response = api_call(self.app, params)
70 response = api_call(self.app, params)
71
71
72 ret = {
72 ret = {
73 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
73 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
74 'success': True,
74 'success': True,
75 'task': None,
75 'task': None,
76 }
76 }
77 expected = ret
77 expected = ret
78 try:
78 try:
79 assert_ok(id_, expected, given=response.body)
79 assert_ok(id_, expected, given=response.body)
80 finally:
80 finally:
81 fixture.destroy_repo(fork_name)
81 fixture.destroy_repo(fork_name)
82
82
83 def test_api_fork_repo_non_admin(self, backend):
83 def test_api_fork_repo_non_admin(self, backend):
84 source_name = backend['minimal'].repo_name
84 source_name = backend['minimal'].repo_name
85 fork_name = backend.new_repo_name()
85 fork_name = backend.new_repo_name()
86
86
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'fork_repo',
88 self.apikey_regular, 'fork_repo',
89 repoid=source_name,
89 repoid=source_name,
90 fork_name=fork_name)
90 fork_name=fork_name)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92
92
93 expected = {
93 expected = {
94 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
94 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
95 'success': True,
95 'success': True,
96 'task': None,
96 'task': None,
97 }
97 }
98 try:
98 try:
99 assert_ok(id_, expected, given=response.body)
99 assert_ok(id_, expected, given=response.body)
100 finally:
100 finally:
101 fixture.destroy_repo(fork_name)
101 fixture.destroy_repo(fork_name)
102
102
103 def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util):
103 def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util):
104 source_name = backend['minimal'].repo_name
104 source_name = backend['minimal'].repo_name
105 repo_group = user_util.create_repo_group()
105 repo_group = user_util.create_repo_group()
106 repo_group_name = repo_group.group_name
106 repo_group_name = repo_group.group_name
107 fork_name = '%s/api-repo-fork' % repo_group_name
107 fork_name = '%s/api-repo-fork' % repo_group_name
108
108
109 id_, params = build_data(
109 id_, params = build_data(
110 self.apikey_regular, 'fork_repo',
110 self.apikey_regular, 'fork_repo',
111 repoid=source_name,
111 repoid=source_name,
112 fork_name=fork_name)
112 fork_name=fork_name)
113 response = api_call(self.app, params)
113 response = api_call(self.app, params)
114
114
115 expected = {
115 expected = {
116 'repo_group': 'Repository group `{}` does not exist'.format(
116 'repo_group': 'Repository group `{}` does not exist'.format(
117 repo_group_name)}
117 repo_group_name)}
118 try:
118 try:
119 assert_error(id_, expected, given=response.body)
119 assert_error(id_, expected, given=response.body)
120 finally:
120 finally:
121 fixture.destroy_repo(fork_name)
121 fixture.destroy_repo(fork_name)
122
122
123 def test_api_fork_repo_non_admin_into_group(self, backend, user_util):
123 def test_api_fork_repo_non_admin_into_group(self, backend, user_util):
124 source_name = backend['minimal'].repo_name
124 source_name = backend['minimal'].repo_name
125 repo_group = user_util.create_repo_group()
125 repo_group = user_util.create_repo_group()
126 fork_name = '%s/api-repo-fork' % repo_group.group_name
126 fork_name = '%s/api-repo-fork' % repo_group.group_name
127
127
128 RepoGroupModel().grant_user_permission(
128 RepoGroupModel().grant_user_permission(
129 repo_group, self.TEST_USER_LOGIN, 'group.admin')
129 repo_group, self.TEST_USER_LOGIN, 'group.admin')
130 Session().commit()
130 Session().commit()
131
131
132 id_, params = build_data(
132 id_, params = build_data(
133 self.apikey_regular, 'fork_repo',
133 self.apikey_regular, 'fork_repo',
134 repoid=source_name,
134 repoid=source_name,
135 fork_name=fork_name)
135 fork_name=fork_name)
136 response = api_call(self.app, params)
136 response = api_call(self.app, params)
137
137
138 expected = {
138 expected = {
139 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
139 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
140 'success': True,
140 'success': True,
141 'task': None,
141 'task': None,
142 }
142 }
143 try:
143 try:
144 assert_ok(id_, expected, given=response.body)
144 assert_ok(id_, expected, given=response.body)
145 finally:
145 finally:
146 fixture.destroy_repo(fork_name)
146 fixture.destroy_repo(fork_name)
147
147
148 def test_api_fork_repo_non_admin_specify_owner(self, backend):
148 def test_api_fork_repo_non_admin_specify_owner(self, backend):
149 source_name = backend['minimal'].repo_name
149 source_name = backend['minimal'].repo_name
150 fork_name = backend.new_repo_name()
150 fork_name = backend.new_repo_name()
151 id_, params = build_data(
151 id_, params = build_data(
152 self.apikey_regular, 'fork_repo',
152 self.apikey_regular, 'fork_repo',
153 repoid=source_name,
153 repoid=source_name,
154 fork_name=fork_name,
154 fork_name=fork_name,
155 owner=TEST_USER_ADMIN_LOGIN)
155 owner=TEST_USER_ADMIN_LOGIN)
156 response = api_call(self.app, params)
156 response = api_call(self.app, params)
157 expected = 'Only RhodeCode super-admin can specify `owner` param'
157 expected = 'Only RhodeCode super-admin can specify `owner` param'
158 assert_error(id_, expected, given=response.body)
158 assert_error(id_, expected, given=response.body)
159
159
160 def test_api_fork_repo_non_admin_no_permission_of_source_repo(
160 def test_api_fork_repo_non_admin_no_permission_of_source_repo(
161 self, backend):
161 self, backend):
162 source_name = backend['minimal'].repo_name
162 source_name = backend['minimal'].repo_name
163 RepoModel().grant_user_permission(repo=source_name,
163 RepoModel().grant_user_permission(repo=source_name,
164 user=self.TEST_USER_LOGIN,
164 user=self.TEST_USER_LOGIN,
165 perm='repository.none')
165 perm='repository.none')
166 fork_name = backend.new_repo_name()
166 fork_name = backend.new_repo_name()
167 id_, params = build_data(
167 id_, params = build_data(
168 self.apikey_regular, 'fork_repo',
168 self.apikey_regular, 'fork_repo',
169 repoid=backend.repo_name,
169 repoid=backend.repo_name,
170 fork_name=fork_name)
170 fork_name=fork_name)
171 response = api_call(self.app, params)
171 response = api_call(self.app, params)
172 expected = 'repository `%s` does not exist' % (backend.repo_name)
172 expected = 'repository `%s` does not exist' % (backend.repo_name)
173 assert_error(id_, expected, given=response.body)
173 assert_error(id_, expected, given=response.body)
174
174
175 def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level(
175 def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level(
176 self, backend, user_util):
176 self, backend, user_util):
177
177
178 regular_user = user_util.create_user()
178 regular_user = user_util.create_user()
179 regular_user_api_key = regular_user.api_key
179 regular_user_api_key = regular_user.api_key
180 usr = UserModel().get_by_username(regular_user.username)
180 usr = UserModel().get_by_username(regular_user.username)
181 usr.inherit_default_permissions = False
181 usr.inherit_default_permissions = False
182 Session().add(usr)
182 Session().add(usr)
183 UserModel().grant_perm(regular_user.username, 'hg.fork.repository')
183 UserModel().grant_perm(regular_user.username, 'hg.fork.repository')
184
184
185 source_name = backend['minimal'].repo_name
185 source_name = backend['minimal'].repo_name
186 fork_name = backend.new_repo_name()
186 fork_name = backend.new_repo_name()
187 id_, params = build_data(
187 id_, params = build_data(
188 regular_user_api_key, 'fork_repo',
188 regular_user_api_key, 'fork_repo',
189 repoid=source_name,
189 repoid=source_name,
190 fork_name=fork_name)
190 fork_name=fork_name)
191 response = api_call(self.app, params)
191 response = api_call(self.app, params)
192 expected = {
192 expected = {
193 "repo_name": "You do not have the permission to "
193 "repo_name": "You do not have the permission to "
194 "store repositories in the root location."}
194 "store repositories in the root location."}
195 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
196
196
197 def test_api_fork_repo_non_admin_no_permission_to_fork(
197 def test_api_fork_repo_non_admin_no_permission_to_fork(
198 self, backend, user_util):
198 self, backend, user_util):
199
199
200 regular_user = user_util.create_user()
200 regular_user = user_util.create_user()
201 regular_user_api_key = regular_user.api_key
201 regular_user_api_key = regular_user.api_key
202 usr = UserModel().get_by_username(regular_user.username)
202 usr = UserModel().get_by_username(regular_user.username)
203 usr.inherit_default_permissions = False
203 usr.inherit_default_permissions = False
204 Session().add(usr)
204 Session().add(usr)
205
205
206 source_name = backend['minimal'].repo_name
206 source_name = backend['minimal'].repo_name
207 fork_name = backend.new_repo_name()
207 fork_name = backend.new_repo_name()
208 id_, params = build_data(
208 id_, params = build_data(
209 regular_user_api_key, 'fork_repo',
209 regular_user_api_key, 'fork_repo',
210 repoid=source_name,
210 repoid=source_name,
211 fork_name=fork_name)
211 fork_name=fork_name)
212 response = api_call(self.app, params)
212 response = api_call(self.app, params)
213
213
214 expected = "Access was denied to this resource."
214 expected = "Access was denied to this resource."
215 assert_error(id_, expected, given=response.body)
215 assert_error(id_, expected, given=response.body)
216
216
217 def test_api_fork_repo_unknown_owner(self, backend):
217 def test_api_fork_repo_unknown_owner(self, backend):
218 source_name = backend['minimal'].repo_name
218 source_name = backend['minimal'].repo_name
219 fork_name = backend.new_repo_name()
219 fork_name = backend.new_repo_name()
220 owner = 'i-dont-exist'
220 owner = 'i-dont-exist'
221 id_, params = build_data(
221 id_, params = build_data(
222 self.apikey, 'fork_repo',
222 self.apikey, 'fork_repo',
223 repoid=source_name,
223 repoid=source_name,
224 fork_name=fork_name,
224 fork_name=fork_name,
225 owner=owner)
225 owner=owner)
226 response = api_call(self.app, params)
226 response = api_call(self.app, params)
227 expected = 'user `%s` does not exist' % (owner,)
227 expected = 'user `%s` does not exist' % (owner,)
228 assert_error(id_, expected, given=response.body)
228 assert_error(id_, expected, given=response.body)
229
229
230 def test_api_fork_repo_fork_exists(self, backend):
230 def test_api_fork_repo_fork_exists(self, backend):
231 source_name = backend['minimal'].repo_name
231 source_name = backend['minimal'].repo_name
232 fork_name = backend.new_repo_name()
232 fork_name = backend.new_repo_name()
233 fork_repo = fixture.create_fork(source_name, fork_name)
233 fork_repo = fixture.create_fork(source_name, fork_name)
234
234
235 id_, params = build_data(
235 id_, params = build_data(
236 self.apikey, 'fork_repo',
236 self.apikey, 'fork_repo',
237 repoid=source_name,
237 repoid=source_name,
238 fork_name=fork_name,
238 fork_name=fork_name,
239 owner=TEST_USER_ADMIN_LOGIN)
239 owner=TEST_USER_ADMIN_LOGIN)
240 response = api_call(self.app, params)
240 response = api_call(self.app, params)
241
241
242 try:
242 try:
243 expected = {
243 expected = {
244 'unique_repo_name': 'Repository with name `{}` already exists'.format(
244 'unique_repo_name': 'Repository with name `{}` already exists'.format(
245 fork_name)}
245 fork_name)}
246 assert_error(id_, expected, given=response.body)
246 assert_error(id_, expected, given=response.body)
247 finally:
247 finally:
248 fixture.destroy_repo(fork_repo.repo_name)
248 fixture.destroy_repo(fork_repo.repo_name)
249
249
250 def test_api_fork_repo_repo_exists(self, backend):
250 def test_api_fork_repo_repo_exists(self, backend):
251 source_name = backend['minimal'].repo_name
251 source_name = backend['minimal'].repo_name
252 fork_name = source_name
252 fork_name = source_name
253
253
254 id_, params = build_data(
254 id_, params = build_data(
255 self.apikey, 'fork_repo',
255 self.apikey, 'fork_repo',
256 repoid=source_name,
256 repoid=source_name,
257 fork_name=fork_name,
257 fork_name=fork_name,
258 owner=TEST_USER_ADMIN_LOGIN)
258 owner=TEST_USER_ADMIN_LOGIN)
259 response = api_call(self.app, params)
259 response = api_call(self.app, params)
260
260
261 expected = {
261 expected = {
262 'unique_repo_name': 'Repository with name `{}` already exists'.format(
262 'unique_repo_name': 'Repository with name `{}` already exists'.format(
263 fork_name)}
263 fork_name)}
264 assert_error(id_, expected, given=response.body)
264 assert_error(id_, expected, given=response.body)
265
265
266 @mock.patch.object(RepoModel, 'create_fork', crash)
266 @mock.patch.object(RepoModel, 'create_fork', crash)
267 def test_api_fork_repo_exception_occurred(self, backend):
267 def test_api_fork_repo_exception_occurred(self, backend):
268 source_name = backend['minimal'].repo_name
268 source_name = backend['minimal'].repo_name
269 fork_name = backend.new_repo_name()
269 fork_name = backend.new_repo_name()
270 id_, params = build_data(
270 id_, params = build_data(
271 self.apikey, 'fork_repo',
271 self.apikey, 'fork_repo',
272 repoid=source_name,
272 repoid=source_name,
273 fork_name=fork_name,
273 fork_name=fork_name,
274 owner=TEST_USER_ADMIN_LOGIN)
274 owner=TEST_USER_ADMIN_LOGIN)
275 response = api_call(self.app, params)
275 response = api_call(self.app, params)
276
276
277 expected = 'failed to fork repository `%s` as `%s`' % (source_name,
277 expected = 'failed to fork repository `%s` as `%s`' % (source_name,
278 fork_name)
278 fork_name)
279 assert_error(id_, expected, given=response.body)
279 assert_error(id_, expected, given=response.body)
@@ -1,101 +1,101 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import Gist
24 from rhodecode.model.db import Gist
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetGist(object):
30 class TestApiGetGist(object):
31 def test_api_get_gist(self, gist_util, http_host_only_stub):
31 def test_api_get_gist(self, gist_util, http_host_only_stub):
32 gist = gist_util.create_gist()
32 gist = gist_util.create_gist()
33 gist_id = gist.gist_access_id
33 gist_id = gist.gist_access_id
34 gist_created_on = gist.created_on
34 gist_created_on = gist.created_on
35 gist_modified_at = gist.modified_at
35 gist_modified_at = gist.modified_at
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'get_gist', gistid=gist_id, )
37 self.apikey, 'get_gist', gistid=gist_id, )
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = {
40 expected = {
41 'access_id': gist_id,
41 'access_id': gist_id,
42 'created_on': gist_created_on,
42 'created_on': gist_created_on,
43 'modified_at': gist_modified_at,
43 'modified_at': gist_modified_at,
44 'description': 'new-gist',
44 'description': 'new-gist',
45 'expires': -1.0,
45 'expires': -1.0,
46 'gist_id': int(gist_id),
46 'gist_id': int(gist_id),
47 'type': 'public',
47 'type': 'public',
48 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
48 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 'content': None,
50 'content': None,
51 }
51 }
52
52
53 assert_ok(id_, expected, given=response.body)
53 assert_ok(id_, expected, given=response.body)
54
54
55 def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
55 def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
56 mapping = {
56 mapping = {
57 u'filename1.txt': {'content': u'hello world'},
57 u'filename1.txt': {'content': u'hello world'},
58 u'filename1ą.txt': {'content': u'hello worldę'}
58 u'filename1ą.txt': {'content': u'hello worldę'}
59 }
59 }
60 gist = gist_util.create_gist(gist_mapping=mapping)
60 gist = gist_util.create_gist(gist_mapping=mapping)
61 gist_id = gist.gist_access_id
61 gist_id = gist.gist_access_id
62 gist_created_on = gist.created_on
62 gist_created_on = gist.created_on
63 gist_modified_at = gist.modified_at
63 gist_modified_at = gist.modified_at
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = {
68 expected = {
69 'access_id': gist_id,
69 'access_id': gist_id,
70 'created_on': gist_created_on,
70 'created_on': gist_created_on,
71 'modified_at': gist_modified_at,
71 'modified_at': gist_modified_at,
72 'description': 'new-gist',
72 'description': 'new-gist',
73 'expires': -1.0,
73 'expires': -1.0,
74 'gist_id': int(gist_id),
74 'gist_id': int(gist_id),
75 'type': 'public',
75 'type': 'public',
76 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
76 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 'content': {
78 'content': {
79 u'filename1.txt': u'hello world',
79 u'filename1.txt': u'hello world',
80 u'filename1ą.txt': u'hello worldę'
80 u'filename1ą.txt': u'hello worldę'
81 },
81 },
82 }
82 }
83
83
84 assert_ok(id_, expected, given=response.body)
84 assert_ok(id_, expected, given=response.body)
85
85
86 def test_api_get_gist_not_existing(self):
86 def test_api_get_gist_not_existing(self):
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'get_gist', gistid='12345', )
88 self.apikey_regular, 'get_gist', gistid='12345', )
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90 expected = 'gist `%s` does not exist' % ('12345',)
90 expected = 'gist `%s` does not exist' % ('12345',)
91 assert_error(id_, expected, given=response.body)
91 assert_error(id_, expected, given=response.body)
92
92
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
94 gist = gist_util.create_gist()
94 gist = gist_util.create_gist()
95 gist_id = gist.gist_access_id
95 gist_id = gist.gist_access_id
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
98 response = api_call(self.app, params)
98 response = api_call(self.app, params)
99
99
100 expected = 'gist `%s` does not exist' % (gist_id,)
100 expected = 'gist `%s` does not exist' % (gist_id,)
101 assert_error(id_, expected, given=response.body)
101 assert_error(id_, expected, given=response.body)
@@ -1,74 +1,74 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error)
26 build_data, api_call, assert_error)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetGist(object):
30 class TestApiGetGist(object):
31 def test_api_get_gists(self, gist_util):
31 def test_api_get_gists(self, gist_util):
32 gist_util.create_gist()
32 gist_util.create_gist()
33 gist_util.create_gist()
33 gist_util.create_gist()
34
34
35 id_, params = build_data(self.apikey, 'get_gists')
35 id_, params = build_data(self.apikey, 'get_gists')
36 response = api_call(self.app, params)
36 response = api_call(self.app, params)
37 assert len(response.json['result']) == 2
37 assert len(response.json['result']) == 2
38
38
39 def test_api_get_gists_regular_user(self, gist_util):
39 def test_api_get_gists_regular_user(self, gist_util):
40 # by admin
40 # by admin
41 gist_util.create_gist()
41 gist_util.create_gist()
42 gist_util.create_gist()
42 gist_util.create_gist()
43
43
44 # by reg user
44 # by reg user
45 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
45 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
46 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
46 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
47 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
47 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
48
48
49 id_, params = build_data(self.apikey_regular, 'get_gists')
49 id_, params = build_data(self.apikey_regular, 'get_gists')
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51 assert len(response.json['result']) == 3
51 assert len(response.json['result']) == 3
52
52
53 def test_api_get_gists_only_for_regular_user(self, gist_util):
53 def test_api_get_gists_only_for_regular_user(self, gist_util):
54 # by admin
54 # by admin
55 gist_util.create_gist()
55 gist_util.create_gist()
56 gist_util.create_gist()
56 gist_util.create_gist()
57
57
58 # by reg user
58 # by reg user
59 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
59 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
60 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
60 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
61 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
61 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
62
62
63 id_, params = build_data(
63 id_, params = build_data(
64 self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN)
64 self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN)
65 response = api_call(self.app, params)
65 response = api_call(self.app, params)
66 assert len(response.json['result']) == 3
66 assert len(response.json['result']) == 3
67
67
68 def test_api_get_gists_regular_user_with_different_userid(self):
68 def test_api_get_gists_regular_user_with_different_userid(self):
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey_regular, 'get_gists',
70 self.apikey_regular, 'get_gists',
71 userid=TEST_USER_ADMIN_LOGIN)
71 userid=TEST_USER_ADMIN_LOGIN)
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73 expected = 'userid is not the same as your user'
73 expected = 'userid is not the same as your user'
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
@@ -1,36 +1,36 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetIp(object):
28 class TestGetIp(object):
29 def test_api_get_ip(self):
29 def test_api_get_ip(self):
30 id_, params = build_data(self.apikey, 'get_ip')
30 id_, params = build_data(self.apikey, 'get_ip')
31 response = api_call(self.app, params)
31 response = api_call(self.app, params)
32 expected = {
32 expected = {
33 'server_ip_addr': '0.0.0.0',
33 'server_ip_addr': '0.0.0.0',
34 'user_ips': []
34 'user_ips': []
35 }
35 }
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
@@ -1,91 +1,91 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import Repository, User
24 from rhodecode.model.db import Repository, User
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error)
27 build_data, api_call, assert_ok, assert_error)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetLocks(object):
31 class TestGetLocks(object):
32 def test_api_get_user_locks_regular_user(self):
32 def test_api_get_user_locks_regular_user(self):
33 id_, params = build_data(self.apikey_regular, 'get_user_locks')
33 id_, params = build_data(self.apikey_regular, 'get_user_locks')
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35 expected = []
35 expected = []
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
37
37
38 def test_api_get_user_locks_with_userid_regular_user(self):
38 def test_api_get_user_locks_with_userid_regular_user(self):
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN)
40 self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN)
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42 expected = 'userid is not the same as your user'
42 expected = 'userid is not the same as your user'
43 assert_error(id_, expected, given=response.body)
43 assert_error(id_, expected, given=response.body)
44
44
45 def test_api_get_user_locks(self):
45 def test_api_get_user_locks(self):
46 id_, params = build_data(self.apikey, 'get_user_locks')
46 id_, params = build_data(self.apikey, 'get_user_locks')
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48 expected = []
48 expected = []
49 assert_ok(id_, expected, given=response.body)
49 assert_ok(id_, expected, given=response.body)
50
50
51 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
51 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
52 ('apikey', True),
52 ('apikey', True),
53 ('apikey_regular', False),
53 ('apikey_regular', False),
54 ])
54 ])
55 def test_api_get_user_locks_with_one_locked_repo(
55 def test_api_get_user_locks_with_one_locked_repo(
56 self, apikey_attr, expect_secrets, backend):
56 self, apikey_attr, expect_secrets, backend):
57
57
58 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
58 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
59 Repository.lock(
59 Repository.lock(
60 repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
60 repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
61
61
62 apikey = getattr(self, apikey_attr)
62 apikey = getattr(self, apikey_attr)
63
63
64 id_, params = build_data(apikey, 'get_user_locks')
64 id_, params = build_data(apikey, 'get_user_locks')
65 if apikey_attr == 'apikey':
65 if apikey_attr == 'apikey':
66 # super-admin should call in specific user
66 # super-admin should call in specific user
67 id_, params = build_data(apikey, 'get_user_locks',
67 id_, params = build_data(apikey, 'get_user_locks',
68 userid=self.TEST_USER_LOGIN)
68 userid=self.TEST_USER_LOGIN)
69
69
70 response = api_call(self.app, params)
70 response = api_call(self.app, params)
71 expected = [repo.get_api_data(include_secrets=expect_secrets)]
71 expected = [repo.get_api_data(include_secrets=expect_secrets)]
72 assert_ok(id_, expected, given=response.body)
72 assert_ok(id_, expected, given=response.body)
73
73
74 def test_api_get_user_locks_with_one_locked_repo_for_specific_user(
74 def test_api_get_user_locks_with_one_locked_repo_for_specific_user(
75 self, backend):
75 self, backend):
76 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
76 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
77
77
78 Repository.lock(repo, User.get_by_username(
78 Repository.lock(repo, User.get_by_username(
79 self.TEST_USER_LOGIN).user_id)
79 self.TEST_USER_LOGIN).user_id)
80 id_, params = build_data(
80 id_, params = build_data(
81 self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN)
81 self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = [repo.get_api_data(include_secrets=True)]
83 expected = [repo.get_api_data(include_secrets=True)]
84 assert_ok(id_, expected, given=response.body)
84 assert_ok(id_, expected, given=response.body)
85
85
86 def test_api_get_user_locks_with_userid(self):
86 def test_api_get_user_locks_with_userid(self):
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN)
88 self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN)
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90 expected = []
90 expected = []
91 assert_ok(id_, expected, given=response.body)
91 assert_ok(id_, expected, given=response.body)
@@ -1,59 +1,59 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetMethod(object):
28 class TestGetMethod(object):
29 def test_get_methods_no_matches(self):
29 def test_get_methods_no_matches(self):
30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
31 response = api_call(self.app, params)
31 response = api_call(self.app, params)
32
32
33 expected = []
33 expected = []
34 assert_ok(id_, expected, given=response.body)
34 assert_ok(id_, expected, given=response.body)
35
35
36 def test_get_methods(self):
36 def test_get_methods(self):
37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = ['changeset_comment', 'comment_pull_request',
40 expected = ['changeset_comment', 'comment_pull_request',
41 'get_pull_request_comments', 'comment_commit']
41 'get_pull_request_comments', 'comment_commit']
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_get_methods_on_single_match(self):
44 def test_get_methods_on_single_match(self):
45 id_, params = build_data(self.apikey, 'get_method',
45 id_, params = build_data(self.apikey, 'get_method',
46 pattern='*comment_commit*')
46 pattern='*comment_commit*')
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48
48
49 expected = ['comment_commit',
49 expected = ['comment_commit',
50 {'apiuser': '<RequiredType>',
50 {'apiuser': '<RequiredType>',
51 'comment_type': "<Optional:u'note'>",
51 'comment_type': "<Optional:u'note'>",
52 'commit_id': '<RequiredType>',
52 'commit_id': '<RequiredType>',
53 'message': '<RequiredType>',
53 'message': '<RequiredType>',
54 'repoid': '<RequiredType>',
54 'repoid': '<RequiredType>',
55 'request': '<RequiredType>',
55 'request': '<RequiredType>',
56 'resolves_comment_id': '<Optional:None>',
56 'resolves_comment_id': '<Optional:None>',
57 'status': '<Optional:None>',
57 'status': '<Optional:None>',
58 'userid': '<Optional:<OptionalAttr:apiuser>>'}]
58 'userid': '<Optional:<OptionalAttr:apiuser>>'}]
59 assert_ok(id_, expected, given=response.body)
59 assert_ok(id_, expected, given=response.body)
@@ -1,142 +1,142 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23 import urlobject
23 import urlobject
24
24
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils2 import safe_unicode
28 from rhodecode.lib.utils2 import safe_unicode
29
29
30 pytestmark = pytest.mark.backends("git", "hg")
30 pytestmark = pytest.mark.backends("git", "hg")
31
31
32
32
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestGetPullRequest(object):
34 class TestGetPullRequest(object):
35
35
36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
37 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.pull_request import PullRequestModel
38 pull_request = pr_util.create_pull_request(mergeable=True)
38 pull_request = pr_util.create_pull_request(mergeable=True)
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'get_pull_request',
40 self.apikey, 'get_pull_request',
41 pullrequestid=pull_request.pull_request_id)
41 pullrequestid=pull_request.pull_request_id)
42
42
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44
44
45 assert response.status == '200 OK'
45 assert response.status == '200 OK'
46
46
47 url_obj = urlobject.URLObject(
47 url_obj = urlobject.URLObject(
48 h.route_url(
48 h.route_url(
49 'pullrequest_show',
49 'pullrequest_show',
50 repo_name=pull_request.target_repo.repo_name,
50 repo_name=pull_request.target_repo.repo_name,
51 pull_request_id=pull_request.pull_request_id))
51 pull_request_id=pull_request.pull_request_id))
52
52
53 pr_url = safe_unicode(
53 pr_url = safe_unicode(
54 url_obj.with_netloc(http_host_only_stub))
54 url_obj.with_netloc(http_host_only_stub))
55 source_url = safe_unicode(
55 source_url = safe_unicode(
56 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
56 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
57 target_url = safe_unicode(
57 target_url = safe_unicode(
58 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
58 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
59 shadow_url = safe_unicode(
59 shadow_url = safe_unicode(
60 PullRequestModel().get_shadow_clone_url(pull_request))
60 PullRequestModel().get_shadow_clone_url(pull_request))
61
61
62 expected = {
62 expected = {
63 'pull_request_id': pull_request.pull_request_id,
63 'pull_request_id': pull_request.pull_request_id,
64 'url': pr_url,
64 'url': pr_url,
65 'title': pull_request.title,
65 'title': pull_request.title,
66 'description': pull_request.description,
66 'description': pull_request.description,
67 'status': pull_request.status,
67 'status': pull_request.status,
68 'created_on': pull_request.created_on,
68 'created_on': pull_request.created_on,
69 'updated_on': pull_request.updated_on,
69 'updated_on': pull_request.updated_on,
70 'commit_ids': pull_request.revisions,
70 'commit_ids': pull_request.revisions,
71 'review_status': pull_request.calculated_review_status(),
71 'review_status': pull_request.calculated_review_status(),
72 'mergeable': {
72 'mergeable': {
73 'status': True,
73 'status': True,
74 'message': 'This pull request can be automatically merged.',
74 'message': 'This pull request can be automatically merged.',
75 },
75 },
76 'source': {
76 'source': {
77 'clone_url': source_url,
77 'clone_url': source_url,
78 'repository': pull_request.source_repo.repo_name,
78 'repository': pull_request.source_repo.repo_name,
79 'reference': {
79 'reference': {
80 'name': pull_request.source_ref_parts.name,
80 'name': pull_request.source_ref_parts.name,
81 'type': pull_request.source_ref_parts.type,
81 'type': pull_request.source_ref_parts.type,
82 'commit_id': pull_request.source_ref_parts.commit_id,
82 'commit_id': pull_request.source_ref_parts.commit_id,
83 },
83 },
84 },
84 },
85 'target': {
85 'target': {
86 'clone_url': target_url,
86 'clone_url': target_url,
87 'repository': pull_request.target_repo.repo_name,
87 'repository': pull_request.target_repo.repo_name,
88 'reference': {
88 'reference': {
89 'name': pull_request.target_ref_parts.name,
89 'name': pull_request.target_ref_parts.name,
90 'type': pull_request.target_ref_parts.type,
90 'type': pull_request.target_ref_parts.type,
91 'commit_id': pull_request.target_ref_parts.commit_id,
91 'commit_id': pull_request.target_ref_parts.commit_id,
92 },
92 },
93 },
93 },
94 'merge': {
94 'merge': {
95 'clone_url': shadow_url,
95 'clone_url': shadow_url,
96 'reference': {
96 'reference': {
97 'name': pull_request.shadow_merge_ref.name,
97 'name': pull_request.shadow_merge_ref.name,
98 'type': pull_request.shadow_merge_ref.type,
98 'type': pull_request.shadow_merge_ref.type,
99 'commit_id': pull_request.shadow_merge_ref.commit_id,
99 'commit_id': pull_request.shadow_merge_ref.commit_id,
100 },
100 },
101 },
101 },
102 'author': pull_request.author.get_api_data(include_secrets=False,
102 'author': pull_request.author.get_api_data(include_secrets=False,
103 details='basic'),
103 details='basic'),
104 'reviewers': [
104 'reviewers': [
105 {
105 {
106 'user': reviewer.get_api_data(include_secrets=False,
106 'user': reviewer.get_api_data(include_secrets=False,
107 details='basic'),
107 details='basic'),
108 'reasons': reasons,
108 'reasons': reasons,
109 'review_status': st[0][1].status if st else 'not_reviewed',
109 'review_status': st[0][1].status if st else 'not_reviewed',
110 }
110 }
111 for obj, reviewer, reasons, mandatory, st in
111 for obj, reviewer, reasons, mandatory, st in
112 pull_request.reviewers_statuses()
112 pull_request.reviewers_statuses()
113 ]
113 ]
114 }
114 }
115 assert_ok(id_, expected, response.body)
115 assert_ok(id_, expected, response.body)
116
116
117 def test_api_get_pull_request_repo_error(self, pr_util):
117 def test_api_get_pull_request_repo_error(self, pr_util):
118 pull_request = pr_util.create_pull_request()
118 pull_request = pr_util.create_pull_request()
119 id_, params = build_data(
119 id_, params = build_data(
120 self.apikey, 'get_pull_request',
120 self.apikey, 'get_pull_request',
121 repoid=666, pullrequestid=pull_request.pull_request_id)
121 repoid=666, pullrequestid=pull_request.pull_request_id)
122 response = api_call(self.app, params)
122 response = api_call(self.app, params)
123
123
124 expected = 'repository `666` does not exist'
124 expected = 'repository `666` does not exist'
125 assert_error(id_, expected, given=response.body)
125 assert_error(id_, expected, given=response.body)
126
126
127 def test_api_get_pull_request_pull_request_error(self):
127 def test_api_get_pull_request_pull_request_error(self):
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey, 'get_pull_request', pullrequestid=666)
129 self.apikey, 'get_pull_request', pullrequestid=666)
130 response = api_call(self.app, params)
130 response = api_call(self.app, params)
131
131
132 expected = 'pull request `666` does not exist'
132 expected = 'pull request `666` does not exist'
133 assert_error(id_, expected, given=response.body)
133 assert_error(id_, expected, given=response.body)
134
134
135 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
135 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
136 id_, params = build_data(
136 id_, params = build_data(
137 self.apikey, 'get_pull_request',
137 self.apikey, 'get_pull_request',
138 pullrequestid=666)
138 pullrequestid=666)
139 response = api_call(self.app, params)
139 response = api_call(self.app, params)
140
140
141 expected = 'pull request `666` does not exist'
141 expected = 'pull request `666` does not exist'
142 assert_error(id_, expected, given=response.body)
142 assert_error(id_, expected, given=response.body)
@@ -1,82 +1,82 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23 import urlobject
23 import urlobject
24
24
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28 from rhodecode.lib.utils2 import safe_unicode
28 from rhodecode.lib.utils2 import safe_unicode
29
29
30 pytestmark = pytest.mark.backends("git", "hg")
30 pytestmark = pytest.mark.backends("git", "hg")
31
31
32
32
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestGetPullRequestComments(object):
34 class TestGetPullRequestComments(object):
35
35
36 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
36 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
37 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.pull_request import PullRequestModel
38
38
39 pull_request = pr_util.create_pull_request(mergeable=True)
39 pull_request = pr_util.create_pull_request(mergeable=True)
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'get_pull_request_comments',
41 self.apikey, 'get_pull_request_comments',
42 pullrequestid=pull_request.pull_request_id)
42 pullrequestid=pull_request.pull_request_id)
43
43
44 response = api_call(self.app, params)
44 response = api_call(self.app, params)
45
45
46 assert response.status == '200 OK'
46 assert response.status == '200 OK'
47 resp_date = response.json['result'][0]['comment_created_on']
47 resp_date = response.json['result'][0]['comment_created_on']
48 resp_comment_id = response.json['result'][0]['comment_id']
48 resp_comment_id = response.json['result'][0]['comment_id']
49
49
50 expected = [
50 expected = [
51 {'comment_author': {'active': True,
51 {'comment_author': {'active': True,
52 'full_name_or_username': 'RhodeCode Admin',
52 'full_name_or_username': 'RhodeCode Admin',
53 'username': 'test_admin'},
53 'username': 'test_admin'},
54 'comment_created_on': resp_date,
54 'comment_created_on': resp_date,
55 'comment_f_path': None,
55 'comment_f_path': None,
56 'comment_id': resp_comment_id,
56 'comment_id': resp_comment_id,
57 'comment_lineno': None,
57 'comment_lineno': None,
58 'comment_status': {'status': 'under_review',
58 'comment_status': {'status': 'under_review',
59 'status_lbl': 'Under Review'},
59 'status_lbl': 'Under Review'},
60 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
60 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
61 'comment_type': 'note',
61 'comment_type': 'note',
62 'pull_request_version': None}
62 'pull_request_version': None}
63 ]
63 ]
64 assert_ok(id_, expected, response.body)
64 assert_ok(id_, expected, response.body)
65
65
66 def test_api_get_pull_request_comments_repo_error(self, pr_util):
66 def test_api_get_pull_request_comments_repo_error(self, pr_util):
67 pull_request = pr_util.create_pull_request()
67 pull_request = pr_util.create_pull_request()
68 id_, params = build_data(
68 id_, params = build_data(
69 self.apikey, 'get_pull_request_comments',
69 self.apikey, 'get_pull_request_comments',
70 repoid=666, pullrequestid=pull_request.pull_request_id)
70 repoid=666, pullrequestid=pull_request.pull_request_id)
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72
72
73 expected = 'repository `666` does not exist'
73 expected = 'repository `666` does not exist'
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
75
75
76 def test_api_get_pull_request_comments_pull_request_error(self):
76 def test_api_get_pull_request_comments_pull_request_error(self):
77 id_, params = build_data(
77 id_, params = build_data(
78 self.apikey, 'get_pull_request_comments', pullrequestid=666)
78 self.apikey, 'get_pull_request_comments', pullrequestid=666)
79 response = api_call(self.app, params)
79 response = api_call(self.app, params)
80
80
81 expected = 'pull request `666` does not exist'
81 expected = 'pull request `666` does not exist'
82 assert_error(id_, expected, given=response.body)
82 assert_error(id_, expected, given=response.body)
@@ -1,80 +1,80 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error)
27 build_data, api_call, assert_error)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetPullRequest(object):
31 class TestGetPullRequest(object):
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_get_pull_requests(self, pr_util):
33 def test_api_get_pull_requests(self, pr_util):
34 pull_request = pr_util.create_pull_request()
34 pull_request = pr_util.create_pull_request()
35 pull_request_2 = PullRequestModel().create(
35 pull_request_2 = PullRequestModel().create(
36 created_by=pull_request.author,
36 created_by=pull_request.author,
37 source_repo=pull_request.source_repo,
37 source_repo=pull_request.source_repo,
38 source_ref=pull_request.source_ref,
38 source_ref=pull_request.source_ref,
39 target_repo=pull_request.target_repo,
39 target_repo=pull_request.target_repo,
40 target_ref=pull_request.target_ref,
40 target_ref=pull_request.target_ref,
41 revisions=pull_request.revisions,
41 revisions=pull_request.revisions,
42 reviewers=(),
42 reviewers=(),
43 title=pull_request.title,
43 title=pull_request.title,
44 description=pull_request.description,
44 description=pull_request.description,
45 )
45 )
46 Session().commit()
46 Session().commit()
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey, 'get_pull_requests',
48 self.apikey, 'get_pull_requests',
49 repoid=pull_request.target_repo.repo_name)
49 repoid=pull_request.target_repo.repo_name)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51 assert response.status == '200 OK'
51 assert response.status == '200 OK'
52 assert len(response.json['result']) == 2
52 assert len(response.json['result']) == 2
53
53
54 PullRequestModel().close_pull_request(
54 PullRequestModel().close_pull_request(
55 pull_request_2, pull_request_2.author)
55 pull_request_2, pull_request_2.author)
56 Session().commit()
56 Session().commit()
57
57
58 id_, params = build_data(
58 id_, params = build_data(
59 self.apikey, 'get_pull_requests',
59 self.apikey, 'get_pull_requests',
60 repoid=pull_request.target_repo.repo_name,
60 repoid=pull_request.target_repo.repo_name,
61 status='new')
61 status='new')
62 response = api_call(self.app, params)
62 response = api_call(self.app, params)
63 assert response.status == '200 OK'
63 assert response.status == '200 OK'
64 assert len(response.json['result']) == 1
64 assert len(response.json['result']) == 1
65
65
66 id_, params = build_data(
66 id_, params = build_data(
67 self.apikey, 'get_pull_requests',
67 self.apikey, 'get_pull_requests',
68 repoid=pull_request.target_repo.repo_name,
68 repoid=pull_request.target_repo.repo_name,
69 status='closed')
69 status='closed')
70 response = api_call(self.app, params)
70 response = api_call(self.app, params)
71 assert response.status == '200 OK'
71 assert response.status == '200 OK'
72 assert len(response.json['result']) == 1
72 assert len(response.json['result']) == 1
73
73
74 @pytest.mark.backends("git", "hg")
74 @pytest.mark.backends("git", "hg")
75 def test_api_get_pull_requests_repo_error(self):
75 def test_api_get_pull_requests_repo_error(self):
76 id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666)
76 id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666)
77 response = api_call(self.app, params)
77 response = api_call(self.app, params)
78
78
79 expected = 'repository `666` does not exist'
79 expected = 'repository `666` does not exist'
80 assert_error(id_, expected, given=response.body)
80 assert_error(id_, expected, given=response.body)
@@ -1,137 +1,137 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, expected_permissions)
29 build_data, api_call, assert_ok, assert_error, expected_permissions)
30
30
31
31
32 @pytest.mark.usefixtures("testuser_api", "app")
32 @pytest.mark.usefixtures("testuser_api", "app")
33 class TestGetRepo(object):
33 class TestGetRepo(object):
34 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
34 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
35 ('apikey', True),
35 ('apikey', True),
36 ('apikey_regular', False),
36 ('apikey_regular', False),
37 ])
37 ])
38 @pytest.mark.parametrize("cache_param", [
38 @pytest.mark.parametrize("cache_param", [
39 True,
39 True,
40 False,
40 False,
41 None,
41 None,
42 ])
42 ])
43 def test_api_get_repo(
43 def test_api_get_repo(
44 self, apikey_attr, expect_secrets, cache_param, backend,
44 self, apikey_attr, expect_secrets, cache_param, backend,
45 user_util):
45 user_util):
46 repo = backend.create_repo()
46 repo = backend.create_repo()
47 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
47 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
48 group = user_util.create_user_group(members=[usr])
48 group = user_util.create_user_group(members=[usr])
49 user_util.grant_user_group_permission_to_repo(
49 user_util.grant_user_group_permission_to_repo(
50 repo=repo, user_group=group, permission_name='repository.read')
50 repo=repo, user_group=group, permission_name='repository.read')
51 Session().commit()
51 Session().commit()
52 kwargs = {
52 kwargs = {
53 'repoid': repo.repo_name,
53 'repoid': repo.repo_name,
54 }
54 }
55 if cache_param is not None:
55 if cache_param is not None:
56 kwargs['cache'] = cache_param
56 kwargs['cache'] = cache_param
57
57
58 apikey = getattr(self, apikey_attr)
58 apikey = getattr(self, apikey_attr)
59 id_, params = build_data(apikey, 'get_repo', **kwargs)
59 id_, params = build_data(apikey, 'get_repo', **kwargs)
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61
61
62 ret = repo.get_api_data()
62 ret = repo.get_api_data()
63
63
64 permissions = expected_permissions(repo)
64 permissions = expected_permissions(repo)
65
65
66 followers = []
66 followers = []
67 for user in repo.followers:
67 for user in repo.followers:
68 followers.append(user.user.get_api_data(
68 followers.append(user.user.get_api_data(
69 include_secrets=expect_secrets))
69 include_secrets=expect_secrets))
70
70
71 ret['permissions'] = permissions
71 ret['permissions'] = permissions
72 ret['followers'] = followers
72 ret['followers'] = followers
73
73
74 expected = ret
74 expected = ret
75
75
76 assert_ok(id_, expected, given=response.body)
76 assert_ok(id_, expected, given=response.body)
77
77
78 @pytest.mark.parametrize("grant_perm", [
78 @pytest.mark.parametrize("grant_perm", [
79 'repository.admin',
79 'repository.admin',
80 'repository.write',
80 'repository.write',
81 'repository.read',
81 'repository.read',
82 ])
82 ])
83 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
83 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
84 # TODO: Depending on which tests are running before this one, we
84 # TODO: Depending on which tests are running before this one, we
85 # start with a different number of permissions in the database.
85 # start with a different number of permissions in the database.
86 repo = RepoModel().get_by_repo_name(backend.repo_name)
86 repo = RepoModel().get_by_repo_name(backend.repo_name)
87 permission_count = len(repo.repo_to_perm)
87 permission_count = len(repo.repo_to_perm)
88
88
89 RepoModel().grant_user_permission(repo=backend.repo_name,
89 RepoModel().grant_user_permission(repo=backend.repo_name,
90 user=self.TEST_USER_LOGIN,
90 user=self.TEST_USER_LOGIN,
91 perm=grant_perm)
91 perm=grant_perm)
92 Session().commit()
92 Session().commit()
93 id_, params = build_data(
93 id_, params = build_data(
94 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
94 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
95 response = api_call(self.app, params)
95 response = api_call(self.app, params)
96
96
97 repo = RepoModel().get_by_repo_name(backend.repo_name)
97 repo = RepoModel().get_by_repo_name(backend.repo_name)
98 ret = repo.get_api_data()
98 ret = repo.get_api_data()
99
99
100 assert permission_count + 1, len(repo.repo_to_perm)
100 assert permission_count + 1, len(repo.repo_to_perm)
101
101
102 permissions = expected_permissions(repo)
102 permissions = expected_permissions(repo)
103
103
104 followers = []
104 followers = []
105 for user in repo.followers:
105 for user in repo.followers:
106 followers.append(user.user.get_api_data())
106 followers.append(user.user.get_api_data())
107
107
108 ret['permissions'] = permissions
108 ret['permissions'] = permissions
109 ret['followers'] = followers
109 ret['followers'] = followers
110
110
111 expected = ret
111 expected = ret
112 try:
112 try:
113 assert_ok(id_, expected, given=response.body)
113 assert_ok(id_, expected, given=response.body)
114 finally:
114 finally:
115 RepoModel().revoke_user_permission(
115 RepoModel().revoke_user_permission(
116 backend.repo_name, self.TEST_USER_LOGIN)
116 backend.repo_name, self.TEST_USER_LOGIN)
117
117
118 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
118 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
119 RepoModel().grant_user_permission(repo=backend.repo_name,
119 RepoModel().grant_user_permission(repo=backend.repo_name,
120 user=self.TEST_USER_LOGIN,
120 user=self.TEST_USER_LOGIN,
121 perm='repository.none')
121 perm='repository.none')
122
122
123 id_, params = build_data(
123 id_, params = build_data(
124 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
124 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
125 response = api_call(self.app, params)
125 response = api_call(self.app, params)
126
126
127 expected = 'repository `%s` does not exist' % (backend.repo_name)
127 expected = 'repository `%s` does not exist' % (backend.repo_name)
128 assert_error(id_, expected, given=response.body)
128 assert_error(id_, expected, given=response.body)
129
129
130 def test_api_get_repo_not_existing(self):
130 def test_api_get_repo_not_existing(self):
131 id_, params = build_data(
131 id_, params = build_data(
132 self.apikey, 'get_repo', repoid='no-such-repo')
132 self.apikey, 'get_repo', repoid='no-such-repo')
133 response = api_call(self.app, params)
133 response = api_call(self.app, params)
134
134
135 ret = 'repository `%s` does not exist' % 'no-such-repo'
135 ret = 'repository `%s` does not exist' % 'no-such-repo'
136 expected = ret
136 expected = ret
137 assert_error(id_, expected, given=response.body)
137 assert_error(id_, expected, given=response.body)
@@ -1,140 +1,140 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_error
24 from rhodecode.api.tests.utils import build_data, api_call, assert_error
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetRepoChangeset(object):
28 class TestGetRepoChangeset(object):
29 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
29 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
30 def test_get_repo_changeset(self, details, backend):
30 def test_get_repo_changeset(self, details, backend):
31 commit = backend.repo.get_commit(commit_idx=0)
31 commit = backend.repo.get_commit(commit_idx=0)
32 __, params = build_data(
32 __, params = build_data(
33 self.apikey, 'get_repo_changeset',
33 self.apikey, 'get_repo_changeset',
34 repoid=backend.repo_name, revision=commit.raw_id,
34 repoid=backend.repo_name, revision=commit.raw_id,
35 details=details,
35 details=details,
36 )
36 )
37 response = api_call(self.app, params)
37 response = api_call(self.app, params)
38 result = response.json['result']
38 result = response.json['result']
39 assert result['revision'] == 0
39 assert result['revision'] == 0
40 assert result['raw_id'] == commit.raw_id
40 assert result['raw_id'] == commit.raw_id
41
41
42 if details == 'full':
42 if details == 'full':
43 assert result['refs']['bookmarks'] == getattr(
43 assert result['refs']['bookmarks'] == getattr(
44 commit, 'bookmarks', [])
44 commit, 'bookmarks', [])
45 assert result['refs']['branches'] == [commit.branch]
45 assert result['refs']['branches'] == [commit.branch]
46 assert result['refs']['tags'] == commit.tags
46 assert result['refs']['tags'] == commit.tags
47
47
48 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
48 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
49 def test_get_repo_changeset_bad_type(self, details, backend):
49 def test_get_repo_changeset_bad_type(self, details, backend):
50 id_, params = build_data(
50 id_, params = build_data(
51 self.apikey, 'get_repo_changeset',
51 self.apikey, 'get_repo_changeset',
52 repoid=backend.repo_name, revision=0,
52 repoid=backend.repo_name, revision=0,
53 details=details,
53 details=details,
54 )
54 )
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56 expected = 'commit_id must be a string value'
56 expected = 'commit_id must be a string value'
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
58
58
59 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
59 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
60 def test_get_repo_changesets(self, details, backend):
60 def test_get_repo_changesets(self, details, backend):
61 limit = 2
61 limit = 2
62 commit = backend.repo.get_commit(commit_idx=0)
62 commit = backend.repo.get_commit(commit_idx=0)
63 __, params = build_data(
63 __, params = build_data(
64 self.apikey, 'get_repo_changesets',
64 self.apikey, 'get_repo_changesets',
65 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
65 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
66 details=details,
66 details=details,
67 )
67 )
68 response = api_call(self.app, params)
68 response = api_call(self.app, params)
69 result = response.json['result']
69 result = response.json['result']
70 assert result
70 assert result
71 assert len(result) == limit
71 assert len(result) == limit
72 for x in xrange(limit):
72 for x in xrange(limit):
73 assert result[x]['revision'] == x
73 assert result[x]['revision'] == x
74
74
75 if details == 'full':
75 if details == 'full':
76 for x in xrange(limit):
76 for x in xrange(limit):
77 assert 'bookmarks' in result[x]['refs']
77 assert 'bookmarks' in result[x]['refs']
78 assert 'branches' in result[x]['refs']
78 assert 'branches' in result[x]['refs']
79 assert 'tags' in result[x]['refs']
79 assert 'tags' in result[x]['refs']
80
80
81 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
81 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
82 @pytest.mark.parametrize("start_rev, expected_revision", [
82 @pytest.mark.parametrize("start_rev, expected_revision", [
83 ("0", 0),
83 ("0", 0),
84 ("10", 10),
84 ("10", 10),
85 ("20", 20),
85 ("20", 20),
86 ])
86 ])
87 @pytest.mark.backends("hg", "git")
87 @pytest.mark.backends("hg", "git")
88 def test_get_repo_changesets_commit_range(
88 def test_get_repo_changesets_commit_range(
89 self, details, backend, start_rev, expected_revision):
89 self, details, backend, start_rev, expected_revision):
90 limit = 10
90 limit = 10
91 __, params = build_data(
91 __, params = build_data(
92 self.apikey, 'get_repo_changesets',
92 self.apikey, 'get_repo_changesets',
93 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
93 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
94 details=details,
94 details=details,
95 )
95 )
96 response = api_call(self.app, params)
96 response = api_call(self.app, params)
97 result = response.json['result']
97 result = response.json['result']
98 assert result
98 assert result
99 assert len(result) == limit
99 assert len(result) == limit
100 for i in xrange(limit):
100 for i in xrange(limit):
101 assert result[i]['revision'] == int(expected_revision) + i
101 assert result[i]['revision'] == int(expected_revision) + i
102
102
103 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
103 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
104 @pytest.mark.parametrize("start_rev, expected_revision", [
104 @pytest.mark.parametrize("start_rev, expected_revision", [
105 ("0", 0),
105 ("0", 0),
106 ("10", 9),
106 ("10", 9),
107 ("20", 19),
107 ("20", 19),
108 ])
108 ])
109 def test_get_repo_changesets_commit_range_svn(
109 def test_get_repo_changesets_commit_range_svn(
110 self, details, backend_svn, start_rev, expected_revision):
110 self, details, backend_svn, start_rev, expected_revision):
111
111
112 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
112 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
113 # in our API allows to pass in a "Commit ID" as well as a
113 # in our API allows to pass in a "Commit ID" as well as a
114 # "Commit Index". In the case of Subversion it is not possible to
114 # "Commit Index". In the case of Subversion it is not possible to
115 # distinguish these cases. As a workaround we implemented this
115 # distinguish these cases. As a workaround we implemented this
116 # behavior which gives a preference to see it as a "Commit ID".
116 # behavior which gives a preference to see it as a "Commit ID".
117
117
118 limit = 10
118 limit = 10
119 __, params = build_data(
119 __, params = build_data(
120 self.apikey, 'get_repo_changesets',
120 self.apikey, 'get_repo_changesets',
121 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
121 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
122 details=details,
122 details=details,
123 )
123 )
124 response = api_call(self.app, params)
124 response = api_call(self.app, params)
125 result = response.json['result']
125 result = response.json['result']
126 assert result
126 assert result
127 assert len(result) == limit
127 assert len(result) == limit
128 for i in xrange(limit):
128 for i in xrange(limit):
129 assert result[i]['revision'] == int(expected_revision) + i
129 assert result[i]['revision'] == int(expected_revision) + i
130
130
131 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
131 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
132 def test_get_repo_changesets_bad_type(self, details, backend):
132 def test_get_repo_changesets_bad_type(self, details, backend):
133 id_, params = build_data(
133 id_, params = build_data(
134 self.apikey, 'get_repo_changesets',
134 self.apikey, 'get_repo_changesets',
135 repoid=backend.repo_name, start_rev=0, limit=2,
135 repoid=backend.repo_name, start_rev=0, limit=2,
136 details=details,
136 details=details,
137 )
137 )
138 response = api_call(self.app, params)
138 response = api_call(self.app, params)
139 expected = 'commit_id must be a string value'
139 expected = 'commit_id must be a string value'
140 assert_error(id_, expected, given=response.body)
140 assert_error(id_, expected, given=response.body)
@@ -1,55 +1,55 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, expected_permissions)
26 build_data, api_call, assert_ok, assert_error, expected_permissions)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetRepoGroup(object):
30 class TestApiGetRepoGroup(object):
31 def test_api_get_repo_group(self, user_util):
31 def test_api_get_repo_group(self, user_util):
32 repo_group = user_util.create_repo_group()
32 repo_group = user_util.create_repo_group()
33 repo_group_name = repo_group.group_name
33 repo_group_name = repo_group.group_name
34
34
35 id_, params = build_data(
35 id_, params = build_data(
36 self.apikey, 'get_repo_group', repogroupid=repo_group_name)
36 self.apikey, 'get_repo_group', repogroupid=repo_group_name)
37 response = api_call(self.app, params)
37 response = api_call(self.app, params)
38
38
39 repo_group = RepoGroupModel()._get_repo_group(repo_group_name)
39 repo_group = RepoGroupModel()._get_repo_group(repo_group_name)
40 ret = repo_group.get_api_data()
40 ret = repo_group.get_api_data()
41
41
42 permissions = expected_permissions(repo_group)
42 permissions = expected_permissions(repo_group)
43
43
44 ret['permissions'] = permissions
44 ret['permissions'] = permissions
45 expected = ret
45 expected = ret
46 assert_ok(id_, expected, given=response.body)
46 assert_ok(id_, expected, given=response.body)
47
47
48 def test_api_get_repo_group_not_existing(self):
48 def test_api_get_repo_group_not_existing(self):
49 id_, params = build_data(
49 id_, params = build_data(
50 self.apikey, 'get_repo_group', repogroupid='no-such-repo-group')
50 self.apikey, 'get_repo_group', repogroupid='no-such-repo-group')
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52
52
53 ret = 'repository group `%s` does not exist' % 'no-such-repo-group'
53 ret = 'repository group `%s` does not exist' % 'no-such-repo-group'
54 expected = ret
54 expected = ret
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
@@ -1,40 +1,40 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApiGetRepoGroups(object):
29 class TestApiGetRepoGroups(object):
30 def test_api_get_repo_groups(self):
30 def test_api_get_repo_groups(self):
31 id_, params = build_data(self.apikey, 'get_repo_groups')
31 id_, params = build_data(self.apikey, 'get_repo_groups')
32 response = api_call(self.app, params)
32 response = api_call(self.app, params)
33
33
34 result = []
34 result = []
35 for repo in RepoGroupModel().get_all():
35 for repo in RepoGroupModel().get_all():
36 result.append(repo.get_api_data())
36 result.append(repo.get_api_data())
37 ret = jsonify(result)
37 ret = jsonify(result)
38
38
39 expected = ret
39 expected = ret
40 assert_ok(id_, expected, given=response.body)
40 assert_ok(id_, expected, given=response.body)
@@ -1,142 +1,142 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetRepoNodes(object):
31 class TestGetRepoNodes(object):
32 @pytest.mark.parametrize("name, ret_type", [
32 @pytest.mark.parametrize("name, ret_type", [
33 ('all', 'all'),
33 ('all', 'all'),
34 ('dirs', 'dirs'),
34 ('dirs', 'dirs'),
35 ('files', 'files'),
35 ('files', 'files'),
36 ])
36 ])
37 def test_api_get_repo_nodes(self, name, ret_type, backend):
37 def test_api_get_repo_nodes(self, name, ret_type, backend):
38 commit_id = 'tip'
38 commit_id = 'tip'
39 path = '/'
39 path = '/'
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'get_repo_nodes',
41 self.apikey, 'get_repo_nodes',
42 repoid=backend.repo_name, revision=commit_id,
42 repoid=backend.repo_name, revision=commit_id,
43 root_path=path,
43 root_path=path,
44 ret_type=ret_type)
44 ret_type=ret_type)
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 # we don't the actual return types here since it's tested somewhere
47 # we don't the actual return types here since it's tested somewhere
48 # else
48 # else
49 expected = response.json['result']
49 expected = response.json['result']
50 assert_ok(id_, expected, given=response.body)
50 assert_ok(id_, expected, given=response.body)
51
51
52 def test_api_get_repo_nodes_bad_commits(self, backend):
52 def test_api_get_repo_nodes_bad_commits(self, backend):
53 commit_id = 'i-dont-exist'
53 commit_id = 'i-dont-exist'
54 path = '/'
54 path = '/'
55 id_, params = build_data(
55 id_, params = build_data(
56 self.apikey, 'get_repo_nodes',
56 self.apikey, 'get_repo_nodes',
57 repoid=backend.repo_name, revision=commit_id,
57 repoid=backend.repo_name, revision=commit_id,
58 root_path=path, )
58 root_path=path, )
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
61 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
62 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
63
63
64 def test_api_get_repo_nodes_bad_path(self, backend):
64 def test_api_get_repo_nodes_bad_path(self, backend):
65 commit_id = 'tip'
65 commit_id = 'tip'
66 path = '/idontexits'
66 path = '/idontexits'
67 id_, params = build_data(
67 id_, params = build_data(
68 self.apikey, 'get_repo_nodes',
68 self.apikey, 'get_repo_nodes',
69 repoid=backend.repo_name, revision=commit_id,
69 repoid=backend.repo_name, revision=commit_id,
70 root_path=path, )
70 root_path=path, )
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72
72
73 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
73 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
75
75
76 def test_api_get_repo_nodes_max_file_bytes(self, backend):
76 def test_api_get_repo_nodes_max_file_bytes(self, backend):
77 commit_id = 'tip'
77 commit_id = 'tip'
78 path = '/'
78 path = '/'
79 max_file_bytes = 500
79 max_file_bytes = 500
80
80
81 id_, params = build_data(
81 id_, params = build_data(
82 self.apikey, 'get_repo_nodes',
82 self.apikey, 'get_repo_nodes',
83 repoid=backend.repo_name, revision=commit_id, details='full',
83 repoid=backend.repo_name, revision=commit_id, details='full',
84 root_path=path)
84 root_path=path)
85 response = api_call(self.app, params)
85 response = api_call(self.app, params)
86 assert any(file['content'] and len(file['content']) > max_file_bytes
86 assert any(file['content'] and len(file['content']) > max_file_bytes
87 for file in response.json['result'])
87 for file in response.json['result'])
88
88
89 id_, params = build_data(
89 id_, params = build_data(
90 self.apikey, 'get_repo_nodes',
90 self.apikey, 'get_repo_nodes',
91 repoid=backend.repo_name, revision=commit_id,
91 repoid=backend.repo_name, revision=commit_id,
92 root_path=path, details='full',
92 root_path=path, details='full',
93 max_file_bytes=max_file_bytes)
93 max_file_bytes=max_file_bytes)
94 response = api_call(self.app, params)
94 response = api_call(self.app, params)
95 assert all(
95 assert all(
96 file['content'] is None if file['size'] > max_file_bytes else True
96 file['content'] is None if file['size'] > max_file_bytes else True
97 for file in response.json['result'])
97 for file in response.json['result'])
98
98
99 def test_api_get_repo_nodes_bad_ret_type(self, backend):
99 def test_api_get_repo_nodes_bad_ret_type(self, backend):
100 commit_id = 'tip'
100 commit_id = 'tip'
101 path = '/'
101 path = '/'
102 ret_type = 'error'
102 ret_type = 'error'
103 id_, params = build_data(
103 id_, params = build_data(
104 self.apikey, 'get_repo_nodes',
104 self.apikey, 'get_repo_nodes',
105 repoid=backend.repo_name, revision=commit_id,
105 repoid=backend.repo_name, revision=commit_id,
106 root_path=path,
106 root_path=path,
107 ret_type=ret_type)
107 ret_type=ret_type)
108 response = api_call(self.app, params)
108 response = api_call(self.app, params)
109
109
110 expected = ('ret_type must be one of %s'
110 expected = ('ret_type must be one of %s'
111 % (','.join(['all', 'dirs', 'files'])))
111 % (','.join(['all', 'dirs', 'files'])))
112 assert_error(id_, expected, given=response.body)
112 assert_error(id_, expected, given=response.body)
113
113
114 @pytest.mark.parametrize("name, ret_type, grant_perm", [
114 @pytest.mark.parametrize("name, ret_type, grant_perm", [
115 ('all', 'all', 'repository.write'),
115 ('all', 'all', 'repository.write'),
116 ('dirs', 'dirs', 'repository.admin'),
116 ('dirs', 'dirs', 'repository.admin'),
117 ('files', 'files', 'repository.read'),
117 ('files', 'files', 'repository.read'),
118 ])
118 ])
119 def test_api_get_repo_nodes_by_regular_user(
119 def test_api_get_repo_nodes_by_regular_user(
120 self, name, ret_type, grant_perm, backend):
120 self, name, ret_type, grant_perm, backend):
121 RepoModel().grant_user_permission(repo=backend.repo_name,
121 RepoModel().grant_user_permission(repo=backend.repo_name,
122 user=self.TEST_USER_LOGIN,
122 user=self.TEST_USER_LOGIN,
123 perm=grant_perm)
123 perm=grant_perm)
124 Session().commit()
124 Session().commit()
125
125
126 commit_id = 'tip'
126 commit_id = 'tip'
127 path = '/'
127 path = '/'
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey_regular, 'get_repo_nodes',
129 self.apikey_regular, 'get_repo_nodes',
130 repoid=backend.repo_name, revision=commit_id,
130 repoid=backend.repo_name, revision=commit_id,
131 root_path=path,
131 root_path=path,
132 ret_type=ret_type)
132 ret_type=ret_type)
133 response = api_call(self.app, params)
133 response = api_call(self.app, params)
134
134
135 # we don't the actual return types here since it's tested somewhere
135 # we don't the actual return types here since it's tested somewhere
136 # else
136 # else
137 expected = response.json['result']
137 expected = response.json['result']
138 try:
138 try:
139 assert_ok(id_, expected, given=response.body)
139 assert_ok(id_, expected, given=response.body)
140 finally:
140 finally:
141 RepoModel().revoke_user_permission(
141 RepoModel().revoke_user_permission(
142 backend.repo_name, self.TEST_USER_LOGIN)
142 backend.repo_name, self.TEST_USER_LOGIN)
@@ -1,40 +1,40 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, expected_permissions)
29 build_data, api_call, assert_ok, assert_error, expected_permissions)
30
30
31
31
32 @pytest.mark.usefixtures("testuser_api", "app")
32 @pytest.mark.usefixtures("testuser_api", "app")
33 class TestGetRepo(object):
33 class TestGetRepo(object):
34 def test_api_get_repo_refs(self, backend, user_util):
34 def test_api_get_repo_refs(self, backend, user_util):
35 repo = backend.create_repo()
35 repo = backend.create_repo()
36 id_, params = build_data(self.apikey, 'get_repo_refs',
36 id_, params = build_data(self.apikey, 'get_repo_refs',
37 **{'repoid': repo.repo_name,})
37 **{'repoid': repo.repo_name,})
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39 expected = repo.scm_instance().refs()
39 expected = repo.scm_instance().refs()
40 assert_ok(id_, expected, given=response.body)
40 assert_ok(id_, expected, given=response.body)
@@ -1,129 +1,129 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, jsonify)
26 build_data, api_call, assert_ok, assert_error, jsonify)
27 from rhodecode.model.db import User
27 from rhodecode.model.db import User
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetRepos(object):
31 class TestGetRepos(object):
32 def test_api_get_repos(self):
32 def test_api_get_repos(self):
33 id_, params = build_data(self.apikey, 'get_repos')
33 id_, params = build_data(self.apikey, 'get_repos')
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35
35
36 result = []
36 result = []
37 for repo in RepoModel().get_all():
37 for repo in RepoModel().get_all():
38 result.append(repo.get_api_data(include_secrets=True))
38 result.append(repo.get_api_data(include_secrets=True))
39 ret = jsonify(result)
39 ret = jsonify(result)
40
40
41 expected = ret
41 expected = ret
42 assert_ok(id_, expected, given=response.body)
42 assert_ok(id_, expected, given=response.body)
43
43
44 def test_api_get_repos_only_toplevel(self, user_util):
44 def test_api_get_repos_only_toplevel(self, user_util):
45 repo_group = user_util.create_repo_group(auto_cleanup=True)
45 repo_group = user_util.create_repo_group(auto_cleanup=True)
46 user_util.create_repo(parent=repo_group)
46 user_util.create_repo(parent=repo_group)
47
47
48 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
48 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
50
51 result = []
51 result = []
52 for repo in RepoModel().get_repos_for_root(root=None):
52 for repo in RepoModel().get_repos_for_root(root=None):
53 result.append(repo.get_api_data(include_secrets=True))
53 result.append(repo.get_api_data(include_secrets=True))
54 expected = jsonify(result)
54 expected = jsonify(result)
55
55
56 assert_ok(id_, expected, given=response.body)
56 assert_ok(id_, expected, given=response.body)
57
57
58 def test_api_get_repos_with_wrong_root(self):
58 def test_api_get_repos_with_wrong_root(self):
59 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
59 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61
61
62 expected = 'Root repository group `abracadabra` does not exist'
62 expected = 'Root repository group `abracadabra` does not exist'
63 assert_error(id_, expected, given=response.body)
63 assert_error(id_, expected, given=response.body)
64
64
65 def test_api_get_repos_with_root(self, user_util):
65 def test_api_get_repos_with_root(self, user_util):
66 repo_group = user_util.create_repo_group(auto_cleanup=True)
66 repo_group = user_util.create_repo_group(auto_cleanup=True)
67 repo_group_name = repo_group.group_name
67 repo_group_name = repo_group.group_name
68
68
69 user_util.create_repo(parent=repo_group)
69 user_util.create_repo(parent=repo_group)
70 user_util.create_repo(parent=repo_group)
70 user_util.create_repo(parent=repo_group)
71
71
72 # nested, should not show up
72 # nested, should not show up
73 user_util._test_name = '{}/'.format(repo_group_name)
73 user_util._test_name = '{}/'.format(repo_group_name)
74 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
74 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
75 user_util.create_repo(parent=sub_repo_group)
75 user_util.create_repo(parent=sub_repo_group)
76
76
77 id_, params = build_data(self.apikey, 'get_repos',
77 id_, params = build_data(self.apikey, 'get_repos',
78 root=repo_group_name, traverse=0)
78 root=repo_group_name, traverse=0)
79 response = api_call(self.app, params)
79 response = api_call(self.app, params)
80
80
81 result = []
81 result = []
82 for repo in RepoModel().get_repos_for_root(repo_group):
82 for repo in RepoModel().get_repos_for_root(repo_group):
83 result.append(repo.get_api_data(include_secrets=True))
83 result.append(repo.get_api_data(include_secrets=True))
84
84
85 assert len(result) == 2
85 assert len(result) == 2
86 expected = jsonify(result)
86 expected = jsonify(result)
87 assert_ok(id_, expected, given=response.body)
87 assert_ok(id_, expected, given=response.body)
88
88
89 def test_api_get_repos_with_root_and_traverse(self, user_util):
89 def test_api_get_repos_with_root_and_traverse(self, user_util):
90 repo_group = user_util.create_repo_group(auto_cleanup=True)
90 repo_group = user_util.create_repo_group(auto_cleanup=True)
91 repo_group_name = repo_group.group_name
91 repo_group_name = repo_group.group_name
92
92
93 user_util.create_repo(parent=repo_group)
93 user_util.create_repo(parent=repo_group)
94 user_util.create_repo(parent=repo_group)
94 user_util.create_repo(parent=repo_group)
95
95
96 # nested, should not show up
96 # nested, should not show up
97 user_util._test_name = '{}/'.format(repo_group_name)
97 user_util._test_name = '{}/'.format(repo_group_name)
98 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
98 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
99 user_util.create_repo(parent=sub_repo_group)
99 user_util.create_repo(parent=sub_repo_group)
100
100
101 id_, params = build_data(self.apikey, 'get_repos',
101 id_, params = build_data(self.apikey, 'get_repos',
102 root=repo_group_name, traverse=1)
102 root=repo_group_name, traverse=1)
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104
104
105 result = []
105 result = []
106 for repo in RepoModel().get_repos_for_root(
106 for repo in RepoModel().get_repos_for_root(
107 repo_group_name, traverse=True):
107 repo_group_name, traverse=True):
108 result.append(repo.get_api_data(include_secrets=True))
108 result.append(repo.get_api_data(include_secrets=True))
109
109
110 assert len(result) == 3
110 assert len(result) == 3
111 expected = jsonify(result)
111 expected = jsonify(result)
112 assert_ok(id_, expected, given=response.body)
112 assert_ok(id_, expected, given=response.body)
113
113
114 def test_api_get_repos_non_admin(self):
114 def test_api_get_repos_non_admin(self):
115 id_, params = build_data(self.apikey_regular, 'get_repos')
115 id_, params = build_data(self.apikey_regular, 'get_repos')
116 response = api_call(self.app, params)
116 response = api_call(self.app, params)
117
117
118 user = User.get_by_username(self.TEST_USER_LOGIN)
118 user = User.get_by_username(self.TEST_USER_LOGIN)
119 allowed_repos = user.AuthUser().permissions['repositories']
119 allowed_repos = user.AuthUser().permissions['repositories']
120
120
121 result = []
121 result = []
122 for repo in RepoModel().get_all():
122 for repo in RepoModel().get_all():
123 perm = allowed_repos[repo.repo_name]
123 perm = allowed_repos[repo.repo_name]
124 if perm in ['repository.read', 'repository.write', 'repository.admin']:
124 if perm in ['repository.read', 'repository.write', 'repository.admin']:
125 result.append(repo.get_api_data())
125 result.append(repo.get_api_data())
126 ret = jsonify(result)
126 ret = jsonify(result)
127
127
128 expected = ret
128 expected = ret
129 assert_ok(id_, expected, given=response.body)
129 assert_ok(id_, expected, given=response.body)
@@ -1,84 +1,84 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.scm import ScmModel
24 from rhodecode.model.scm import ScmModel
25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
26
26
27
27
28 @pytest.fixture
28 @pytest.fixture
29 def http_host_stub():
29 def http_host_stub():
30 """
30 """
31 To ensure that we can get an IP address, this test shall run with a
31 To ensure that we can get an IP address, this test shall run with a
32 hostname set to "localhost".
32 hostname set to "localhost".
33 """
33 """
34 return 'localhost:80'
34 return 'localhost:80'
35
35
36
36
37 @pytest.mark.usefixtures("testuser_api", "app")
37 @pytest.mark.usefixtures("testuser_api", "app")
38 class TestGetServerInfo(object):
38 class TestGetServerInfo(object):
39 def test_api_get_server_info(self):
39 def test_api_get_server_info(self):
40 id_, params = build_data(self.apikey, 'get_server_info')
40 id_, params = build_data(self.apikey, 'get_server_info')
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42 resp = response.json
42 resp = response.json
43 expected = ScmModel().get_server_info()
43 expected = ScmModel().get_server_info()
44 expected['memory'] = resp['result']['memory']
44 expected['memory'] = resp['result']['memory']
45 expected['uptime'] = resp['result']['uptime']
45 expected['uptime'] = resp['result']['uptime']
46 expected['load'] = resp['result']['load']
46 expected['load'] = resp['result']['load']
47 expected['cpu'] = resp['result']['cpu']
47 expected['cpu'] = resp['result']['cpu']
48 expected['storage'] = resp['result']['storage']
48 expected['storage'] = resp['result']['storage']
49 expected['storage_temp'] = resp['result']['storage_temp']
49 expected['storage_temp'] = resp['result']['storage_temp']
50 expected['storage_inodes'] = resp['result']['storage_inodes']
50 expected['storage_inodes'] = resp['result']['storage_inodes']
51 expected['server'] = resp['result']['server']
51 expected['server'] = resp['result']['server']
52
52
53 expected['index_storage'] = resp['result']['index_storage']
53 expected['index_storage'] = resp['result']['index_storage']
54 expected['storage'] = resp['result']['storage']
54 expected['storage'] = resp['result']['storage']
55
55
56 assert_ok(id_, expected, given=response.body)
56 assert_ok(id_, expected, given=response.body)
57
57
58 def test_api_get_server_info_ip(self):
58 def test_api_get_server_info_ip(self):
59 id_, params = build_data(self.apikey, 'get_server_info')
59 id_, params = build_data(self.apikey, 'get_server_info')
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61 resp = response.json
61 resp = response.json
62 expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'})
62 expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'})
63 expected['memory'] = resp['result']['memory']
63 expected['memory'] = resp['result']['memory']
64 expected['uptime'] = resp['result']['uptime']
64 expected['uptime'] = resp['result']['uptime']
65 expected['load'] = resp['result']['load']
65 expected['load'] = resp['result']['load']
66 expected['cpu'] = resp['result']['cpu']
66 expected['cpu'] = resp['result']['cpu']
67 expected['storage'] = resp['result']['storage']
67 expected['storage'] = resp['result']['storage']
68 expected['storage_temp'] = resp['result']['storage_temp']
68 expected['storage_temp'] = resp['result']['storage_temp']
69 expected['storage_inodes'] = resp['result']['storage_inodes']
69 expected['storage_inodes'] = resp['result']['storage_inodes']
70 expected['server'] = resp['result']['server']
70 expected['server'] = resp['result']['server']
71
71
72 expected['index_storage'] = resp['result']['index_storage']
72 expected['index_storage'] = resp['result']['index_storage']
73 expected['storage'] = resp['result']['storage']
73 expected['storage'] = resp['result']['storage']
74
74
75 assert_ok(id_, expected, given=response.body)
75 assert_ok(id_, expected, given=response.body)
76
76
77 def test_api_get_server_info_data_for_search_index_build(self):
77 def test_api_get_server_info_data_for_search_index_build(self):
78 id_, params = build_data(self.apikey, 'get_server_info')
78 id_, params = build_data(self.apikey, 'get_server_info')
79 response = api_call(self.app, params)
79 response = api_call(self.app, params)
80 resp = response.json
80 resp = response.json
81
81
82 # required by indexer
82 # required by indexer
83 assert resp['result']['index_storage']
83 assert resp['result']['index_storage']
84 assert resp['result']['storage']
84 assert resp['result']['storage']
@@ -1,86 +1,86 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.auth import AuthUser
23 from rhodecode.lib.auth import AuthUser
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error)
27 build_data, api_call, assert_ok, assert_error)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetUser(object):
31 class TestGetUser(object):
32 def test_api_get_user(self):
32 def test_api_get_user(self):
33 id_, params = build_data(
33 id_, params = build_data(
34 self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN)
34 self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN)
35 response = api_call(self.app, params)
35 response = api_call(self.app, params)
36
36
37 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
37 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
38 ret = usr.get_api_data(include_secrets=True)
38 ret = usr.get_api_data(include_secrets=True)
39 permissions = AuthUser(usr.user_id).permissions
39 permissions = AuthUser(usr.user_id).permissions
40 ret['permissions'] = permissions
40 ret['permissions'] = permissions
41 ret['permissions_summary'] = permissions
41 ret['permissions_summary'] = permissions
42
42
43 expected = ret
43 expected = ret
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 def test_api_get_user_not_existing(self):
46 def test_api_get_user_not_existing(self):
47 id_, params = build_data(self.apikey, 'get_user', userid='trololo')
47 id_, params = build_data(self.apikey, 'get_user', userid='trololo')
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = "user `%s` does not exist" % 'trololo'
50 expected = "user `%s` does not exist" % 'trololo'
51 assert_error(id_, expected, given=response.body)
51 assert_error(id_, expected, given=response.body)
52
52
53 def test_api_get_user_without_giving_userid(self):
53 def test_api_get_user_without_giving_userid(self):
54 id_, params = build_data(self.apikey, 'get_user')
54 id_, params = build_data(self.apikey, 'get_user')
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56
56
57 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
57 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
58 ret = usr.get_api_data(include_secrets=True)
58 ret = usr.get_api_data(include_secrets=True)
59 permissions = AuthUser(usr.user_id).permissions
59 permissions = AuthUser(usr.user_id).permissions
60 ret['permissions'] = permissions
60 ret['permissions'] = permissions
61 ret['permissions_summary'] = permissions
61 ret['permissions_summary'] = permissions
62
62
63 expected = ret
63 expected = ret
64 assert_ok(id_, expected, given=response.body)
64 assert_ok(id_, expected, given=response.body)
65
65
66 def test_api_get_user_without_giving_userid_non_admin(self):
66 def test_api_get_user_without_giving_userid_non_admin(self):
67 id_, params = build_data(self.apikey_regular, 'get_user')
67 id_, params = build_data(self.apikey_regular, 'get_user')
68 response = api_call(self.app, params)
68 response = api_call(self.app, params)
69
69
70 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
70 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
71 ret = usr.get_api_data(include_secrets=True)
71 ret = usr.get_api_data(include_secrets=True)
72 permissions = AuthUser(usr.user_id).permissions
72 permissions = AuthUser(usr.user_id).permissions
73 ret['permissions'] = permissions
73 ret['permissions'] = permissions
74 ret['permissions_summary'] = permissions
74 ret['permissions_summary'] = permissions
75
75
76 expected = ret
76 expected = ret
77 assert_ok(id_, expected, given=response.body)
77 assert_ok(id_, expected, given=response.body)
78
78
79 def test_api_get_user_with_giving_userid_non_admin(self):
79 def test_api_get_user_with_giving_userid_non_admin(self):
80 id_, params = build_data(
80 id_, params = build_data(
81 self.apikey_regular, 'get_user',
81 self.apikey_regular, 'get_user',
82 userid=self.TEST_USER_LOGIN)
82 userid=self.TEST_USER_LOGIN)
83 response = api_call(self.app, params)
83 response = api_call(self.app, params)
84
84
85 expected = 'userid is not the same as your user'
85 expected = 'userid is not the same as your user'
86 assert_error(id_, expected, given=response.body)
86 assert_error(id_, expected, given=response.body)
@@ -1,76 +1,76 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error, expected_permissions)
25 build_data, api_call, assert_ok, assert_error, expected_permissions)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestGetUserGroups(object):
29 class TestGetUserGroups(object):
30 def test_api_get_user_group(self, user_util):
30 def test_api_get_user_group(self, user_util):
31 user, group = user_util.create_user_with_group()
31 user, group = user_util.create_user_with_group()
32 id_, params = build_data(
32 id_, params = build_data(
33 self.apikey, 'get_user_group', usergroupid=group.users_group_name)
33 self.apikey, 'get_user_group', usergroupid=group.users_group_name)
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35
35
36 ret = group.get_api_data()
36 ret = group.get_api_data()
37 ret['users'] = [user.get_api_data()]
37 ret['users'] = [user.get_api_data()]
38
38
39 permissions = expected_permissions(group)
39 permissions = expected_permissions(group)
40
40
41 ret['permissions'] = permissions
41 ret['permissions'] = permissions
42 ret['permissions_summary'] = response.json['result']['permissions_summary']
42 ret['permissions_summary'] = response.json['result']['permissions_summary']
43 expected = ret
43 expected = ret
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 def test_api_get_user_group_regular_user(self, user_util):
46 def test_api_get_user_group_regular_user(self, user_util):
47 user, group = user_util.create_user_with_group()
47 user, group = user_util.create_user_with_group()
48 id_, params = build_data(
48 id_, params = build_data(
49 self.apikey_regular, 'get_user_group',
49 self.apikey_regular, 'get_user_group',
50 usergroupid=group.users_group_name)
50 usergroupid=group.users_group_name)
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52
52
53 ret = group.get_api_data()
53 ret = group.get_api_data()
54 ret['users'] = [user.get_api_data()]
54 ret['users'] = [user.get_api_data()]
55
55
56 permissions = expected_permissions(group)
56 permissions = expected_permissions(group)
57
57
58 ret['permissions'] = permissions
58 ret['permissions'] = permissions
59 ret['permissions_summary'] = response.json['result']['permissions_summary']
59 ret['permissions_summary'] = response.json['result']['permissions_summary']
60 expected = ret
60 expected = ret
61 assert_ok(id_, expected, given=response.body)
61 assert_ok(id_, expected, given=response.body)
62
62
63 def test_api_get_user_group_regular_user_permission_denied(
63 def test_api_get_user_group_regular_user_permission_denied(
64 self, user_util):
64 self, user_util):
65 group = user_util.create_user_group()
65 group = user_util.create_user_group()
66 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
66 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
67 group_name = group.users_group_name
67 group_name = group.users_group_name
68 user_util.grant_user_permission_to_user_group(
68 user_util.grant_user_permission_to_user_group(
69 group, user, 'usergroup.none')
69 group, user, 'usergroup.none')
70
70
71 id_, params = build_data(
71 id_, params = build_data(
72 self.apikey_regular, 'get_user_group', usergroupid=group_name)
72 self.apikey_regular, 'get_user_group', usergroupid=group_name)
73 response = api_call(self.app, params)
73 response = api_call(self.app, params)
74
74
75 expected = 'user group `%s` does not exist' % (group_name,)
75 expected = 'user group `%s` does not exist' % (group_name,)
76 assert_error(id_, expected, given=response.body)
76 assert_error(id_, expected, given=response.body)
@@ -1,71 +1,71 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import json
22 import json
23
23
24 import pytest
24 import pytest
25
25
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.api.tests.utils import build_data, api_call
27 from rhodecode.api.tests.utils import build_data, api_call
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGetUserGroups(object):
31 class TestGetUserGroups(object):
32 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
32 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
33 ('apikey', True),
33 ('apikey', True),
34 ('apikey_regular', False),
34 ('apikey_regular', False),
35 ])
35 ])
36 def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util):
36 def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util):
37 first_group = user_util.create_user_group()
37 first_group = user_util.create_user_group()
38 second_group = user_util.create_user_group()
38 second_group = user_util.create_user_group()
39 expected = [
39 expected = [
40 g.get_api_data(include_secrets=expect_secrets)
40 g.get_api_data(include_secrets=expect_secrets)
41 for g in (first_group, second_group)]
41 for g in (first_group, second_group)]
42
42
43 apikey = getattr(self, apikey_attr)
43 apikey = getattr(self, apikey_attr)
44 id_, params = build_data(apikey, 'get_user_groups', )
44 id_, params = build_data(apikey, 'get_user_groups', )
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46 self._assert_ok(id_, expected, response)
46 self._assert_ok(id_, expected, response)
47
47
48 def test_api_get_user_groups_regular_user(self, user_util):
48 def test_api_get_user_groups_regular_user(self, user_util):
49 first_group = user_util.create_user_group()
49 first_group = user_util.create_user_group()
50 second_group = user_util.create_user_group()
50 second_group = user_util.create_user_group()
51 expected = [g.get_api_data() for g in (first_group, second_group)]
51 expected = [g.get_api_data() for g in (first_group, second_group)]
52
52
53 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
53 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
54 response = api_call(self.app, params)
54 response = api_call(self.app, params)
55 self._assert_ok(id_, expected, response)
55 self._assert_ok(id_, expected, response)
56
56
57 def test_api_get_user_groups_regular_user_no_permission(self, user_util):
57 def test_api_get_user_groups_regular_user_no_permission(self, user_util):
58 group = user_util.create_user_group()
58 group = user_util.create_user_group()
59 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
59 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
60 user_util.grant_user_permission_to_user_group(
60 user_util.grant_user_permission_to_user_group(
61 group, user, 'usergroup.none')
61 group, user, 'usergroup.none')
62 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
62 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
63 response = api_call(self.app, params)
63 response = api_call(self.app, params)
64 expected = []
64 expected = []
65 self._assert_ok(id_, expected, response)
65 self._assert_ok(id_, expected, response)
66
66
67 def _assert_ok(self, id_, expected_list, response):
67 def _assert_ok(self, id_, expected_list, response):
68 result = json.loads(response.body)
68 result = json.loads(response.body)
69 assert result['id'] == id_
69 assert result['id'] == id_
70 assert result['error'] is None
70 assert result['error'] is None
71 assert sorted(result['result']) == sorted(expected_list)
71 assert sorted(result['result']) == sorted(expected_list)
@@ -1,40 +1,40 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import User
23 from rhodecode.model.db import User
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, jsonify)
25 build_data, api_call, assert_ok, jsonify)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestGetUsers(object):
29 class TestGetUsers(object):
30 def test_api_get_users(self):
30 def test_api_get_users(self):
31 id_, params = build_data(self.apikey, 'get_users', )
31 id_, params = build_data(self.apikey, 'get_users', )
32 response = api_call(self.app, params)
32 response = api_call(self.app, params)
33 ret_all = []
33 ret_all = []
34 _users = User.query().filter(User.username != User.DEFAULT_USER) \
34 _users = User.query().filter(User.username != User.DEFAULT_USER) \
35 .order_by(User.username).all()
35 .order_by(User.username).all()
36 for usr in _users:
36 for usr in _users:
37 ret = usr.get_api_data(include_secrets=True)
37 ret = usr.get_api_data(include_secrets=True)
38 ret_all.append(jsonify(ret))
38 ret_all.append(jsonify(ret))
39 expected = ret_all
39 expected = ret_all
40 assert_ok(id_, expected, given=response.body)
40 assert_ok(id_, expected, given=response.body)
@@ -1,90 +1,90 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGrantUserGroupPermission(object):
30 class TestGrantUserGroupPermission(object):
31 @pytest.mark.parametrize("name, perm", [
31 @pytest.mark.parametrize("name, perm", [
32 ('none', 'repository.none'),
32 ('none', 'repository.none'),
33 ('read', 'repository.read'),
33 ('read', 'repository.read'),
34 ('write', 'repository.write'),
34 ('write', 'repository.write'),
35 ('admin', 'repository.admin')
35 ('admin', 'repository.admin')
36 ])
36 ])
37 def test_api_grant_user_group_permission(
37 def test_api_grant_user_group_permission(
38 self, name, perm, backend, user_util):
38 self, name, perm, backend, user_util):
39 user_group = user_util.create_user_group()
39 user_group = user_util.create_user_group()
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey,
41 self.apikey,
42 'grant_user_group_permission',
42 'grant_user_group_permission',
43 repoid=backend.repo_name,
43 repoid=backend.repo_name,
44 usergroupid=user_group.users_group_name,
44 usergroupid=user_group.users_group_name,
45 perm=perm)
45 perm=perm)
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 ret = {
48 ret = {
49 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
49 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
50 perm, user_group.users_group_name, backend.repo_name
50 perm, user_group.users_group_name, backend.repo_name
51 ),
51 ),
52 'success': True
52 'success': True
53 }
53 }
54 expected = ret
54 expected = ret
55 assert_ok(id_, expected, given=response.body)
55 assert_ok(id_, expected, given=response.body)
56
56
57 def test_api_grant_user_group_permission_wrong_permission(
57 def test_api_grant_user_group_permission_wrong_permission(
58 self, backend, user_util):
58 self, backend, user_util):
59 perm = 'haha.no.permission'
59 perm = 'haha.no.permission'
60 user_group = user_util.create_user_group()
60 user_group = user_util.create_user_group()
61 id_, params = build_data(
61 id_, params = build_data(
62 self.apikey,
62 self.apikey,
63 'grant_user_group_permission',
63 'grant_user_group_permission',
64 repoid=backend.repo_name,
64 repoid=backend.repo_name,
65 usergroupid=user_group.users_group_name,
65 usergroupid=user_group.users_group_name,
66 perm=perm)
66 perm=perm)
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 expected = 'permission `%s` does not exist' % (perm,)
69 expected = 'permission `%s` does not exist' % (perm,)
70 assert_error(id_, expected, given=response.body)
70 assert_error(id_, expected, given=response.body)
71
71
72 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
72 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
73 def test_api_grant_user_group_permission_exception_when_adding(
73 def test_api_grant_user_group_permission_exception_when_adding(
74 self, backend, user_util):
74 self, backend, user_util):
75 perm = 'repository.read'
75 perm = 'repository.read'
76 user_group = user_util.create_user_group()
76 user_group = user_util.create_user_group()
77 id_, params = build_data(
77 id_, params = build_data(
78 self.apikey,
78 self.apikey,
79 'grant_user_group_permission',
79 'grant_user_group_permission',
80 repoid=backend.repo_name,
80 repoid=backend.repo_name,
81 usergroupid=user_group.users_group_name,
81 usergroupid=user_group.users_group_name,
82 perm=perm)
82 perm=perm)
83 response = api_call(self.app, params)
83 response = api_call(self.app, params)
84
84
85 expected = (
85 expected = (
86 'failed to edit permission for user group: `%s` in repo: `%s`' % (
86 'failed to edit permission for user group: `%s` in repo: `%s`' % (
87 user_group.users_group_name, backend.repo_name
87 user_group.users_group_name, backend.repo_name
88 )
88 )
89 )
89 )
90 assert_error(id_, expected, given=response.body)
90 assert_error(id_, expected, given=response.body)
@@ -1,172 +1,172 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGrantUserGroupPermissionFromRepoGroup(object):
31 class TestGrantUserGroupPermissionFromRepoGroup(object):
32 @pytest.mark.parametrize("name, perm, apply_to_children", [
32 @pytest.mark.parametrize("name, perm, apply_to_children", [
33 ('none', 'group.none', 'none'),
33 ('none', 'group.none', 'none'),
34 ('read', 'group.read', 'none'),
34 ('read', 'group.read', 'none'),
35 ('write', 'group.write', 'none'),
35 ('write', 'group.write', 'none'),
36 ('admin', 'group.admin', 'none'),
36 ('admin', 'group.admin', 'none'),
37
37
38 ('none', 'group.none', 'all'),
38 ('none', 'group.none', 'all'),
39 ('read', 'group.read', 'all'),
39 ('read', 'group.read', 'all'),
40 ('write', 'group.write', 'all'),
40 ('write', 'group.write', 'all'),
41 ('admin', 'group.admin', 'all'),
41 ('admin', 'group.admin', 'all'),
42
42
43 ('none', 'group.none', 'repos'),
43 ('none', 'group.none', 'repos'),
44 ('read', 'group.read', 'repos'),
44 ('read', 'group.read', 'repos'),
45 ('write', 'group.write', 'repos'),
45 ('write', 'group.write', 'repos'),
46 ('admin', 'group.admin', 'repos'),
46 ('admin', 'group.admin', 'repos'),
47
47
48 ('none', 'group.none', 'groups'),
48 ('none', 'group.none', 'groups'),
49 ('read', 'group.read', 'groups'),
49 ('read', 'group.read', 'groups'),
50 ('write', 'group.write', 'groups'),
50 ('write', 'group.write', 'groups'),
51 ('admin', 'group.admin', 'groups'),
51 ('admin', 'group.admin', 'groups'),
52 ])
52 ])
53 def test_api_grant_user_group_permission_to_repo_group(
53 def test_api_grant_user_group_permission_to_repo_group(
54 self, name, perm, apply_to_children, user_util):
54 self, name, perm, apply_to_children, user_util):
55 user_group = user_util.create_user_group()
55 user_group = user_util.create_user_group()
56 repo_group = user_util.create_repo_group()
56 repo_group = user_util.create_repo_group()
57 id_, params = build_data(
57 id_, params = build_data(
58 self.apikey,
58 self.apikey,
59 'grant_user_group_permission_to_repo_group',
59 'grant_user_group_permission_to_repo_group',
60 repogroupid=repo_group.name,
60 repogroupid=repo_group.name,
61 usergroupid=user_group.users_group_name,
61 usergroupid=user_group.users_group_name,
62 perm=perm,
62 perm=perm,
63 apply_to_children=apply_to_children,)
63 apply_to_children=apply_to_children,)
64 response = api_call(self.app, params)
64 response = api_call(self.app, params)
65
65
66 ret = {
66 ret = {
67 'msg': (
67 'msg': (
68 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
68 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
69 ' in repo group: `%s`' % (
69 ' in repo group: `%s`' % (
70 perm, apply_to_children, user_group.users_group_name,
70 perm, apply_to_children, user_group.users_group_name,
71 repo_group.name
71 repo_group.name
72 )
72 )
73 ),
73 ),
74 'success': True
74 'success': True
75 }
75 }
76 expected = ret
76 expected = ret
77 try:
77 try:
78 assert_ok(id_, expected, given=response.body)
78 assert_ok(id_, expected, given=response.body)
79 finally:
79 finally:
80 RepoGroupModel().revoke_user_group_permission(
80 RepoGroupModel().revoke_user_group_permission(
81 repo_group.group_id, user_group.users_group_id)
81 repo_group.group_id, user_group.users_group_id)
82
82
83 @pytest.mark.parametrize(
83 @pytest.mark.parametrize(
84 "name, perm, apply_to_children, grant_admin, access_ok", [
84 "name, perm, apply_to_children, grant_admin, access_ok", [
85 ('none_fails', 'group.none', 'none', False, False),
85 ('none_fails', 'group.none', 'none', False, False),
86 ('read_fails', 'group.read', 'none', False, False),
86 ('read_fails', 'group.read', 'none', False, False),
87 ('write_fails', 'group.write', 'none', False, False),
87 ('write_fails', 'group.write', 'none', False, False),
88 ('admin_fails', 'group.admin', 'none', False, False),
88 ('admin_fails', 'group.admin', 'none', False, False),
89
89
90 # with granted perms
90 # with granted perms
91 ('none_ok', 'group.none', 'none', True, True),
91 ('none_ok', 'group.none', 'none', True, True),
92 ('read_ok', 'group.read', 'none', True, True),
92 ('read_ok', 'group.read', 'none', True, True),
93 ('write_ok', 'group.write', 'none', True, True),
93 ('write_ok', 'group.write', 'none', True, True),
94 ('admin_ok', 'group.admin', 'none', True, True),
94 ('admin_ok', 'group.admin', 'none', True, True),
95 ]
95 ]
96 )
96 )
97 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
97 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
98 self, name, perm, apply_to_children, grant_admin, access_ok,
98 self, name, perm, apply_to_children, grant_admin, access_ok,
99 user_util):
99 user_util):
100 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
100 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
101 user_group = user_util.create_user_group()
101 user_group = user_util.create_user_group()
102 repo_group = user_util.create_repo_group()
102 repo_group = user_util.create_repo_group()
103 if grant_admin:
103 if grant_admin:
104 user_util.grant_user_permission_to_repo_group(
104 user_util.grant_user_permission_to_repo_group(
105 repo_group, user, 'group.admin')
105 repo_group, user, 'group.admin')
106
106
107 id_, params = build_data(
107 id_, params = build_data(
108 self.apikey_regular,
108 self.apikey_regular,
109 'grant_user_group_permission_to_repo_group',
109 'grant_user_group_permission_to_repo_group',
110 repogroupid=repo_group.name,
110 repogroupid=repo_group.name,
111 usergroupid=user_group.users_group_name,
111 usergroupid=user_group.users_group_name,
112 perm=perm,
112 perm=perm,
113 apply_to_children=apply_to_children,)
113 apply_to_children=apply_to_children,)
114 response = api_call(self.app, params)
114 response = api_call(self.app, params)
115 if access_ok:
115 if access_ok:
116 ret = {
116 ret = {
117 'msg': (
117 'msg': (
118 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
118 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
119 ' in repo group: `%s`' % (
119 ' in repo group: `%s`' % (
120 perm, apply_to_children, user_group.users_group_name,
120 perm, apply_to_children, user_group.users_group_name,
121 repo_group.name
121 repo_group.name
122 )
122 )
123 ),
123 ),
124 'success': True
124 'success': True
125 }
125 }
126 expected = ret
126 expected = ret
127 try:
127 try:
128 assert_ok(id_, expected, given=response.body)
128 assert_ok(id_, expected, given=response.body)
129 finally:
129 finally:
130 RepoGroupModel().revoke_user_group_permission(
130 RepoGroupModel().revoke_user_group_permission(
131 repo_group.group_id, user_group.users_group_id)
131 repo_group.group_id, user_group.users_group_id)
132 else:
132 else:
133 expected = 'repository group `%s` does not exist' % (
133 expected = 'repository group `%s` does not exist' % (
134 repo_group.name,)
134 repo_group.name,)
135 assert_error(id_, expected, given=response.body)
135 assert_error(id_, expected, given=response.body)
136
136
137 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
137 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
138 self, user_util):
138 self, user_util):
139 user_group = user_util.create_user_group()
139 user_group = user_util.create_user_group()
140 repo_group = user_util.create_repo_group()
140 repo_group = user_util.create_repo_group()
141 perm = 'haha.no.permission'
141 perm = 'haha.no.permission'
142 id_, params = build_data(
142 id_, params = build_data(
143 self.apikey,
143 self.apikey,
144 'grant_user_group_permission_to_repo_group',
144 'grant_user_group_permission_to_repo_group',
145 repogroupid=repo_group.name,
145 repogroupid=repo_group.name,
146 usergroupid=user_group.users_group_name,
146 usergroupid=user_group.users_group_name,
147 perm=perm)
147 perm=perm)
148 response = api_call(self.app, params)
148 response = api_call(self.app, params)
149
149
150 expected = 'permission `%s` does not exist' % (perm,)
150 expected = 'permission `%s` does not exist' % (perm,)
151 assert_error(id_, expected, given=response.body)
151 assert_error(id_, expected, given=response.body)
152
152
153 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
153 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
154 def test_api_grant_user_group_permission_exception_when_adding_2(
154 def test_api_grant_user_group_permission_exception_when_adding_2(
155 self, user_util):
155 self, user_util):
156 user_group = user_util.create_user_group()
156 user_group = user_util.create_user_group()
157 repo_group = user_util.create_repo_group()
157 repo_group = user_util.create_repo_group()
158 perm = 'group.read'
158 perm = 'group.read'
159 id_, params = build_data(
159 id_, params = build_data(
160 self.apikey,
160 self.apikey,
161 'grant_user_group_permission_to_repo_group',
161 'grant_user_group_permission_to_repo_group',
162 repogroupid=repo_group.name,
162 repogroupid=repo_group.name,
163 usergroupid=user_group.users_group_name,
163 usergroupid=user_group.users_group_name,
164 perm=perm)
164 perm=perm)
165 response = api_call(self.app, params)
165 response = api_call(self.app, params)
166
166
167 expected = (
167 expected = (
168 'failed to edit permission for user group: `%s`'
168 'failed to edit permission for user group: `%s`'
169 ' in repo group: `%s`' % (
169 ' in repo group: `%s`' % (
170 user_group.users_group_name, repo_group.name)
170 user_group.users_group_name, repo_group.name)
171 )
171 )
172 assert_error(id_, expected, given=response.body)
172 assert_error(id_, expected, given=response.body)
@@ -1,97 +1,97 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user_group import UserGroupModel
23 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error)
25 build_data, api_call, assert_ok, assert_error)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestGrantUserGroupPermissionFromUserGroup(object):
29 class TestGrantUserGroupPermissionFromUserGroup(object):
30 @pytest.mark.parametrize("name, perm", [
30 @pytest.mark.parametrize("name, perm", [
31 ('none', 'usergroup.none'),
31 ('none', 'usergroup.none'),
32 ('read', 'usergroup.read'),
32 ('read', 'usergroup.read'),
33 ('write', 'usergroup.write'),
33 ('write', 'usergroup.write'),
34 ('admin', 'usergroup.admin'),
34 ('admin', 'usergroup.admin'),
35
35
36 ('none', 'usergroup.none'),
36 ('none', 'usergroup.none'),
37 ('read', 'usergroup.read'),
37 ('read', 'usergroup.read'),
38 ('write', 'usergroup.write'),
38 ('write', 'usergroup.write'),
39 ('admin', 'usergroup.admin'),
39 ('admin', 'usergroup.admin'),
40
40
41 ('none', 'usergroup.none'),
41 ('none', 'usergroup.none'),
42 ('read', 'usergroup.read'),
42 ('read', 'usergroup.read'),
43 ('write', 'usergroup.write'),
43 ('write', 'usergroup.write'),
44 ('admin', 'usergroup.admin'),
44 ('admin', 'usergroup.admin'),
45
45
46 ('none', 'usergroup.none'),
46 ('none', 'usergroup.none'),
47 ('read', 'usergroup.read'),
47 ('read', 'usergroup.read'),
48 ('write', 'usergroup.write'),
48 ('write', 'usergroup.write'),
49 ('admin', 'usergroup.admin'),
49 ('admin', 'usergroup.admin'),
50 ])
50 ])
51 def test_api_grant_user_group_permission_to_user_group(
51 def test_api_grant_user_group_permission_to_user_group(
52 self, name, perm, user_util):
52 self, name, perm, user_util):
53 group = user_util.create_user_group()
53 group = user_util.create_user_group()
54 target_group = user_util.create_user_group()
54 target_group = user_util.create_user_group()
55
55
56 id_, params = build_data(
56 id_, params = build_data(
57 self.apikey,
57 self.apikey,
58 'grant_user_group_permission_to_user_group',
58 'grant_user_group_permission_to_user_group',
59 usergroupid=target_group.users_group_name,
59 usergroupid=target_group.users_group_name,
60 sourceusergroupid=group.users_group_name,
60 sourceusergroupid=group.users_group_name,
61 perm=perm)
61 perm=perm)
62 response = api_call(self.app, params)
62 response = api_call(self.app, params)
63
63
64 expected = {
64 expected = {
65 'msg': (
65 'msg': (
66 'Granted perm: `%s` for user group: `%s`'
66 'Granted perm: `%s` for user group: `%s`'
67 ' in user group: `%s`' % (
67 ' in user group: `%s`' % (
68 perm, group.users_group_name,
68 perm, group.users_group_name,
69 target_group.users_group_name
69 target_group.users_group_name
70 )
70 )
71 ),
71 ),
72 'success': True
72 'success': True
73 }
73 }
74 try:
74 try:
75 assert_ok(id_, expected, given=response.body)
75 assert_ok(id_, expected, given=response.body)
76 finally:
76 finally:
77 UserGroupModel().revoke_user_group_permission(
77 UserGroupModel().revoke_user_group_permission(
78 target_group.users_group_id, group.users_group_id)
78 target_group.users_group_id, group.users_group_id)
79
79
80 def test_api_grant_user_group_permission_to_user_group_same_failure(
80 def test_api_grant_user_group_permission_to_user_group_same_failure(
81 self, user_util):
81 self, user_util):
82 group = user_util.create_user_group()
82 group = user_util.create_user_group()
83
83
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey,
85 self.apikey,
86 'grant_user_group_permission_to_user_group',
86 'grant_user_group_permission_to_user_group',
87 usergroupid=group.users_group_name,
87 usergroupid=group.users_group_name,
88 sourceusergroupid=group.users_group_name,
88 sourceusergroupid=group.users_group_name,
89 perm='usergroup.none')
89 perm='usergroup.none')
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91
91
92 expected = (
92 expected = (
93 'failed to edit permission for user group: `%s`'
93 'failed to edit permission for user group: `%s`'
94 ' in user group: `%s`' % (
94 ' in user group: `%s`' % (
95 group.users_group_name, group.users_group_name)
95 group.users_group_name, group.users_group_name)
96 )
96 )
97 assert_error(id_, expected, given=response.body)
97 assert_error(id_, expected, given=response.body)
@@ -1,87 +1,87 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGrantUserPermission(object):
30 class TestGrantUserPermission(object):
31 @pytest.mark.parametrize("name, perm", [
31 @pytest.mark.parametrize("name, perm", [
32 ('none', 'repository.none'),
32 ('none', 'repository.none'),
33 ('read', 'repository.read'),
33 ('read', 'repository.read'),
34 ('write', 'repository.write'),
34 ('write', 'repository.write'),
35 ('admin', 'repository.admin')
35 ('admin', 'repository.admin')
36 ])
36 ])
37 def test_api_grant_user_permission(self, name, perm, backend, user_util):
37 def test_api_grant_user_permission(self, name, perm, backend, user_util):
38 user = user_util.create_user()
38 user = user_util.create_user()
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey,
40 self.apikey,
41 'grant_user_permission',
41 'grant_user_permission',
42 repoid=backend.repo_name,
42 repoid=backend.repo_name,
43 userid=user.username,
43 userid=user.username,
44 perm=perm)
44 perm=perm)
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 ret = {
47 ret = {
48 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
48 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
49 perm, user.username, backend.repo_name
49 perm, user.username, backend.repo_name
50 ),
50 ),
51 'success': True
51 'success': True
52 }
52 }
53 expected = ret
53 expected = ret
54 assert_ok(id_, expected, given=response.body)
54 assert_ok(id_, expected, given=response.body)
55
55
56 def test_api_grant_user_permission_wrong_permission(
56 def test_api_grant_user_permission_wrong_permission(
57 self, backend, user_util):
57 self, backend, user_util):
58 user = user_util.create_user()
58 user = user_util.create_user()
59 perm = 'haha.no.permission'
59 perm = 'haha.no.permission'
60 id_, params = build_data(
60 id_, params = build_data(
61 self.apikey,
61 self.apikey,
62 'grant_user_permission',
62 'grant_user_permission',
63 repoid=backend.repo_name,
63 repoid=backend.repo_name,
64 userid=user.username,
64 userid=user.username,
65 perm=perm)
65 perm=perm)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = 'permission `%s` does not exist' % (perm,)
68 expected = 'permission `%s` does not exist' % (perm,)
69 assert_error(id_, expected, given=response.body)
69 assert_error(id_, expected, given=response.body)
70
70
71 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
71 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
72 def test_api_grant_user_permission_exception_when_adding(
72 def test_api_grant_user_permission_exception_when_adding(
73 self, backend, user_util):
73 self, backend, user_util):
74 user = user_util.create_user()
74 user = user_util.create_user()
75 perm = 'repository.read'
75 perm = 'repository.read'
76 id_, params = build_data(
76 id_, params = build_data(
77 self.apikey,
77 self.apikey,
78 'grant_user_permission',
78 'grant_user_permission',
79 repoid=backend.repo_name,
79 repoid=backend.repo_name,
80 userid=user.username,
80 userid=user.username,
81 perm=perm)
81 perm=perm)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83
83
84 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
84 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
85 user.username, backend.repo_name
85 user.username, backend.repo_name
86 )
86 )
87 assert_error(id_, expected, given=response.body)
87 assert_error(id_, expected, given=response.body)
@@ -1,157 +1,157 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGrantUserPermissionFromRepoGroup(object):
31 class TestGrantUserPermissionFromRepoGroup(object):
32 @pytest.mark.parametrize("name, perm, apply_to_children", [
32 @pytest.mark.parametrize("name, perm, apply_to_children", [
33 ('none', 'group.none', 'none'),
33 ('none', 'group.none', 'none'),
34 ('read', 'group.read', 'none'),
34 ('read', 'group.read', 'none'),
35 ('write', 'group.write', 'none'),
35 ('write', 'group.write', 'none'),
36 ('admin', 'group.admin', 'none'),
36 ('admin', 'group.admin', 'none'),
37
37
38 ('none', 'group.none', 'all'),
38 ('none', 'group.none', 'all'),
39 ('read', 'group.read', 'all'),
39 ('read', 'group.read', 'all'),
40 ('write', 'group.write', 'all'),
40 ('write', 'group.write', 'all'),
41 ('admin', 'group.admin', 'all'),
41 ('admin', 'group.admin', 'all'),
42
42
43 ('none', 'group.none', 'repos'),
43 ('none', 'group.none', 'repos'),
44 ('read', 'group.read', 'repos'),
44 ('read', 'group.read', 'repos'),
45 ('write', 'group.write', 'repos'),
45 ('write', 'group.write', 'repos'),
46 ('admin', 'group.admin', 'repos'),
46 ('admin', 'group.admin', 'repos'),
47
47
48 ('none', 'group.none', 'groups'),
48 ('none', 'group.none', 'groups'),
49 ('read', 'group.read', 'groups'),
49 ('read', 'group.read', 'groups'),
50 ('write', 'group.write', 'groups'),
50 ('write', 'group.write', 'groups'),
51 ('admin', 'group.admin', 'groups'),
51 ('admin', 'group.admin', 'groups'),
52 ])
52 ])
53 def test_api_grant_user_permission_to_repo_group(
53 def test_api_grant_user_permission_to_repo_group(
54 self, name, perm, apply_to_children, user_util):
54 self, name, perm, apply_to_children, user_util):
55 user = user_util.create_user()
55 user = user_util.create_user()
56 repo_group = user_util.create_repo_group()
56 repo_group = user_util.create_repo_group()
57 id_, params = build_data(
57 id_, params = build_data(
58 self.apikey, 'grant_user_permission_to_repo_group',
58 self.apikey, 'grant_user_permission_to_repo_group',
59 repogroupid=repo_group.name, userid=user.username,
59 repogroupid=repo_group.name, userid=user.username,
60 perm=perm, apply_to_children=apply_to_children)
60 perm=perm, apply_to_children=apply_to_children)
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62
62
63 ret = {
63 ret = {
64 'msg': (
64 'msg': (
65 'Granted perm: `%s` (recursive:%s) for user: `%s`'
65 'Granted perm: `%s` (recursive:%s) for user: `%s`'
66 ' in repo group: `%s`' % (
66 ' in repo group: `%s`' % (
67 perm, apply_to_children, user.username, repo_group.name
67 perm, apply_to_children, user.username, repo_group.name
68 )
68 )
69 ),
69 ),
70 'success': True
70 'success': True
71 }
71 }
72 expected = ret
72 expected = ret
73 assert_ok(id_, expected, given=response.body)
73 assert_ok(id_, expected, given=response.body)
74
74
75 @pytest.mark.parametrize(
75 @pytest.mark.parametrize(
76 "name, perm, apply_to_children, grant_admin, access_ok", [
76 "name, perm, apply_to_children, grant_admin, access_ok", [
77 ('none_fails', 'group.none', 'none', False, False),
77 ('none_fails', 'group.none', 'none', False, False),
78 ('read_fails', 'group.read', 'none', False, False),
78 ('read_fails', 'group.read', 'none', False, False),
79 ('write_fails', 'group.write', 'none', False, False),
79 ('write_fails', 'group.write', 'none', False, False),
80 ('admin_fails', 'group.admin', 'none', False, False),
80 ('admin_fails', 'group.admin', 'none', False, False),
81
81
82 # with granted perms
82 # with granted perms
83 ('none_ok', 'group.none', 'none', True, True),
83 ('none_ok', 'group.none', 'none', True, True),
84 ('read_ok', 'group.read', 'none', True, True),
84 ('read_ok', 'group.read', 'none', True, True),
85 ('write_ok', 'group.write', 'none', True, True),
85 ('write_ok', 'group.write', 'none', True, True),
86 ('admin_ok', 'group.admin', 'none', True, True),
86 ('admin_ok', 'group.admin', 'none', True, True),
87 ]
87 ]
88 )
88 )
89 def test_api_grant_user_permission_to_repo_group_by_regular_user(
89 def test_api_grant_user_permission_to_repo_group_by_regular_user(
90 self, name, perm, apply_to_children, grant_admin, access_ok,
90 self, name, perm, apply_to_children, grant_admin, access_ok,
91 user_util):
91 user_util):
92 user = user_util.create_user()
92 user = user_util.create_user()
93 repo_group = user_util.create_repo_group()
93 repo_group = user_util.create_repo_group()
94
94
95 if grant_admin:
95 if grant_admin:
96 test_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
96 test_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
97 user_util.grant_user_permission_to_repo_group(
97 user_util.grant_user_permission_to_repo_group(
98 repo_group, test_user, 'group.admin')
98 repo_group, test_user, 'group.admin')
99
99
100 id_, params = build_data(
100 id_, params = build_data(
101 self.apikey_regular, 'grant_user_permission_to_repo_group',
101 self.apikey_regular, 'grant_user_permission_to_repo_group',
102 repogroupid=repo_group.name, userid=user.username,
102 repogroupid=repo_group.name, userid=user.username,
103 perm=perm, apply_to_children=apply_to_children)
103 perm=perm, apply_to_children=apply_to_children)
104 response = api_call(self.app, params)
104 response = api_call(self.app, params)
105 if access_ok:
105 if access_ok:
106 ret = {
106 ret = {
107 'msg': (
107 'msg': (
108 'Granted perm: `%s` (recursive:%s) for user: `%s`'
108 'Granted perm: `%s` (recursive:%s) for user: `%s`'
109 ' in repo group: `%s`' % (
109 ' in repo group: `%s`' % (
110 perm, apply_to_children, user.username, repo_group.name
110 perm, apply_to_children, user.username, repo_group.name
111 )
111 )
112 ),
112 ),
113 'success': True
113 'success': True
114 }
114 }
115 expected = ret
115 expected = ret
116 assert_ok(id_, expected, given=response.body)
116 assert_ok(id_, expected, given=response.body)
117 else:
117 else:
118 expected = 'repository group `%s` does not exist' % (
118 expected = 'repository group `%s` does not exist' % (
119 repo_group.name, )
119 repo_group.name, )
120 assert_error(id_, expected, given=response.body)
120 assert_error(id_, expected, given=response.body)
121
121
122 def test_api_grant_user_permission_to_repo_group_wrong_permission(
122 def test_api_grant_user_permission_to_repo_group_wrong_permission(
123 self, user_util):
123 self, user_util):
124 user = user_util.create_user()
124 user = user_util.create_user()
125 repo_group = user_util.create_repo_group()
125 repo_group = user_util.create_repo_group()
126 perm = 'haha.no.permission'
126 perm = 'haha.no.permission'
127 id_, params = build_data(
127 id_, params = build_data(
128 self.apikey,
128 self.apikey,
129 'grant_user_permission_to_repo_group',
129 'grant_user_permission_to_repo_group',
130 repogroupid=repo_group.name,
130 repogroupid=repo_group.name,
131 userid=user.username,
131 userid=user.username,
132 perm=perm)
132 perm=perm)
133 response = api_call(self.app, params)
133 response = api_call(self.app, params)
134
134
135 expected = 'permission `%s` does not exist' % (perm,)
135 expected = 'permission `%s` does not exist' % (perm,)
136 assert_error(id_, expected, given=response.body)
136 assert_error(id_, expected, given=response.body)
137
137
138 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
138 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
139 def test_api_grant_user_permission_to_repo_group_exception_when_adding(
139 def test_api_grant_user_permission_to_repo_group_exception_when_adding(
140 self, user_util):
140 self, user_util):
141 user = user_util.create_user()
141 user = user_util.create_user()
142 repo_group = user_util.create_repo_group()
142 repo_group = user_util.create_repo_group()
143 perm = 'group.read'
143 perm = 'group.read'
144 id_, params = build_data(
144 id_, params = build_data(
145 self.apikey,
145 self.apikey,
146 'grant_user_permission_to_repo_group',
146 'grant_user_permission_to_repo_group',
147 repogroupid=repo_group.name,
147 repogroupid=repo_group.name,
148 userid=user.username,
148 userid=user.username,
149 perm=perm)
149 perm=perm)
150 response = api_call(self.app, params)
150 response = api_call(self.app, params)
151
151
152 expected = (
152 expected = (
153 'failed to edit permission for user: `%s` in repo group: `%s`' % (
153 'failed to edit permission for user: `%s` in repo group: `%s`' % (
154 user.username, repo_group.name
154 user.username, repo_group.name
155 )
155 )
156 )
156 )
157 assert_error(id_, expected, given=response.body)
157 assert_error(id_, expected, given=response.body)
@@ -1,156 +1,156 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestGrantUserPermissionFromUserGroup(object):
31 class TestGrantUserPermissionFromUserGroup(object):
32 @pytest.mark.parametrize("name, perm", [
32 @pytest.mark.parametrize("name, perm", [
33 ('none', 'usergroup.none'),
33 ('none', 'usergroup.none'),
34 ('read', 'usergroup.read'),
34 ('read', 'usergroup.read'),
35 ('write', 'usergroup.write'),
35 ('write', 'usergroup.write'),
36 ('admin', 'usergroup.admin'),
36 ('admin', 'usergroup.admin'),
37
37
38 ('none', 'usergroup.none'),
38 ('none', 'usergroup.none'),
39 ('read', 'usergroup.read'),
39 ('read', 'usergroup.read'),
40 ('write', 'usergroup.write'),
40 ('write', 'usergroup.write'),
41 ('admin', 'usergroup.admin'),
41 ('admin', 'usergroup.admin'),
42
42
43 ('none', 'usergroup.none'),
43 ('none', 'usergroup.none'),
44 ('read', 'usergroup.read'),
44 ('read', 'usergroup.read'),
45 ('write', 'usergroup.write'),
45 ('write', 'usergroup.write'),
46 ('admin', 'usergroup.admin'),
46 ('admin', 'usergroup.admin'),
47
47
48 ('none', 'usergroup.none'),
48 ('none', 'usergroup.none'),
49 ('read', 'usergroup.read'),
49 ('read', 'usergroup.read'),
50 ('write', 'usergroup.write'),
50 ('write', 'usergroup.write'),
51 ('admin', 'usergroup.admin'),
51 ('admin', 'usergroup.admin'),
52 ])
52 ])
53 def test_api_grant_user_permission_to_user_group(
53 def test_api_grant_user_permission_to_user_group(
54 self, name, perm, user_util):
54 self, name, perm, user_util):
55 user = user_util.create_user()
55 user = user_util.create_user()
56 group = user_util.create_user_group()
56 group = user_util.create_user_group()
57 id_, params = build_data(
57 id_, params = build_data(
58 self.apikey,
58 self.apikey,
59 'grant_user_permission_to_user_group',
59 'grant_user_permission_to_user_group',
60 usergroupid=group.users_group_name,
60 usergroupid=group.users_group_name,
61 userid=user.username,
61 userid=user.username,
62 perm=perm)
62 perm=perm)
63 response = api_call(self.app, params)
63 response = api_call(self.app, params)
64
64
65 ret = {
65 ret = {
66 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
66 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
67 perm, user.username, group.users_group_name
67 perm, user.username, group.users_group_name
68 ),
68 ),
69 'success': True
69 'success': True
70 }
70 }
71 expected = ret
71 expected = ret
72 assert_ok(id_, expected, given=response.body)
72 assert_ok(id_, expected, given=response.body)
73
73
74 @pytest.mark.parametrize("name, perm, grant_admin, access_ok", [
74 @pytest.mark.parametrize("name, perm, grant_admin, access_ok", [
75 ('none_fails', 'usergroup.none', False, False),
75 ('none_fails', 'usergroup.none', False, False),
76 ('read_fails', 'usergroup.read', False, False),
76 ('read_fails', 'usergroup.read', False, False),
77 ('write_fails', 'usergroup.write', False, False),
77 ('write_fails', 'usergroup.write', False, False),
78 ('admin_fails', 'usergroup.admin', False, False),
78 ('admin_fails', 'usergroup.admin', False, False),
79
79
80 # with granted perms
80 # with granted perms
81 ('none_ok', 'usergroup.none', True, True),
81 ('none_ok', 'usergroup.none', True, True),
82 ('read_ok', 'usergroup.read', True, True),
82 ('read_ok', 'usergroup.read', True, True),
83 ('write_ok', 'usergroup.write', True, True),
83 ('write_ok', 'usergroup.write', True, True),
84 ('admin_ok', 'usergroup.admin', True, True),
84 ('admin_ok', 'usergroup.admin', True, True),
85 ])
85 ])
86 def test_api_grant_user_permission_to_user_group_by_regular_user(
86 def test_api_grant_user_permission_to_user_group_by_regular_user(
87 self, name, perm, grant_admin, access_ok, user_util):
87 self, name, perm, grant_admin, access_ok, user_util):
88 api_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
88 api_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
89 user = user_util.create_user()
89 user = user_util.create_user()
90 group = user_util.create_user_group()
90 group = user_util.create_user_group()
91 # grant the user ability to at least read the group
91 # grant the user ability to at least read the group
92 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
92 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
93 user_util.grant_user_permission_to_user_group(
93 user_util.grant_user_permission_to_user_group(
94 group, api_user, permission)
94 group, api_user, permission)
95
95
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey_regular,
97 self.apikey_regular,
98 'grant_user_permission_to_user_group',
98 'grant_user_permission_to_user_group',
99 usergroupid=group.users_group_name,
99 usergroupid=group.users_group_name,
100 userid=user.username,
100 userid=user.username,
101 perm=perm)
101 perm=perm)
102 response = api_call(self.app, params)
102 response = api_call(self.app, params)
103
103
104 if access_ok:
104 if access_ok:
105 ret = {
105 ret = {
106 'msg': (
106 'msg': (
107 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
107 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
108 perm, user.username, group.users_group_name
108 perm, user.username, group.users_group_name
109 )
109 )
110 ),
110 ),
111 'success': True
111 'success': True
112 }
112 }
113 expected = ret
113 expected = ret
114 assert_ok(id_, expected, given=response.body)
114 assert_ok(id_, expected, given=response.body)
115 else:
115 else:
116 expected = 'user group `%s` does not exist' % (
116 expected = 'user group `%s` does not exist' % (
117 group.users_group_name)
117 group.users_group_name)
118 assert_error(id_, expected, given=response.body)
118 assert_error(id_, expected, given=response.body)
119
119
120 def test_api_grant_user_permission_to_user_group_wrong_permission(
120 def test_api_grant_user_permission_to_user_group_wrong_permission(
121 self, user_util):
121 self, user_util):
122 user = user_util.create_user()
122 user = user_util.create_user()
123 group = user_util.create_user_group()
123 group = user_util.create_user_group()
124 perm = 'haha.no.permission'
124 perm = 'haha.no.permission'
125 id_, params = build_data(
125 id_, params = build_data(
126 self.apikey,
126 self.apikey,
127 'grant_user_permission_to_user_group',
127 'grant_user_permission_to_user_group',
128 usergroupid=group.users_group_name,
128 usergroupid=group.users_group_name,
129 userid=user.username,
129 userid=user.username,
130 perm=perm)
130 perm=perm)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132
132
133 expected = 'permission `%s` does not exist' % perm
133 expected = 'permission `%s` does not exist' % perm
134 assert_error(id_, expected, given=response.body)
134 assert_error(id_, expected, given=response.body)
135
135
136 def test_api_grant_user_permission_to_user_group_exception_when_adding(
136 def test_api_grant_user_permission_to_user_group_exception_when_adding(
137 self, user_util):
137 self, user_util):
138 user = user_util.create_user()
138 user = user_util.create_user()
139 group = user_util.create_user_group()
139 group = user_util.create_user_group()
140
140
141 perm = 'usergroup.read'
141 perm = 'usergroup.read'
142 id_, params = build_data(
142 id_, params = build_data(
143 self.apikey,
143 self.apikey,
144 'grant_user_permission_to_user_group',
144 'grant_user_permission_to_user_group',
145 usergroupid=group.users_group_name,
145 usergroupid=group.users_group_name,
146 userid=user.username,
146 userid=user.username,
147 perm=perm)
147 perm=perm)
148 with mock.patch.object(UserGroupModel, 'grant_user_permission', crash):
148 with mock.patch.object(UserGroupModel, 'grant_user_permission', crash):
149 response = api_call(self.app, params)
149 response = api_call(self.app, params)
150
150
151 expected = (
151 expected = (
152 'failed to edit permission for user: `%s` in user group: `%s`' % (
152 'failed to edit permission for user: `%s` in user group: `%s`' % (
153 user.username, group.users_group_name
153 user.username, group.users_group_name
154 )
154 )
155 )
155 )
156 assert_error(id_, expected, given=response.body)
156 assert_error(id_, expected, given=response.body)
@@ -1,68 +1,68 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.scm import ScmModel
24 from rhodecode.model.scm import ScmModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, crash)
26 build_data, api_call, assert_ok, assert_error, crash)
27 from rhodecode.model.repo import RepoModel
27 from rhodecode.model.repo import RepoModel
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestInvalidateCache(object):
31 class TestInvalidateCache(object):
32
32
33 def _set_cache(self, repo_name):
33 def _set_cache(self, repo_name):
34 repo = RepoModel().get_by_repo_name(repo_name)
34 repo = RepoModel().get_by_repo_name(repo_name)
35 repo.scm_instance(cache=True)
35 repo.scm_instance(cache=True)
36
36
37 def test_api_invalidate_cache(self, backend):
37 def test_api_invalidate_cache(self, backend):
38 self._set_cache(backend.repo_name)
38 self._set_cache(backend.repo_name)
39
39
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
41 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43
43
44 expected = {
44 expected = {
45 'msg': "Cache for repository `%s` was invalidated" % (
45 'msg': "Cache for repository `%s` was invalidated" % (
46 backend.repo_name,),
46 backend.repo_name,),
47 'repository': backend.repo_name,
47 'repository': backend.repo_name,
48 }
48 }
49 assert_ok(id_, expected, given=response.body)
49 assert_ok(id_, expected, given=response.body)
50
50
51 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
51 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
52 def test_api_invalidate_cache_error(self, backend):
52 def test_api_invalidate_cache_error(self, backend):
53 id_, params = build_data(
53 id_, params = build_data(
54 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
54 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56
56
57 expected = 'Error occurred during cache invalidation action'
57 expected = 'Error occurred during cache invalidation action'
58 assert_error(id_, expected, given=response.body)
58 assert_error(id_, expected, given=response.body)
59
59
60 def test_api_invalidate_cache_regular_user_no_permission(self, backend):
60 def test_api_invalidate_cache_regular_user_no_permission(self, backend):
61 self._set_cache(backend.repo_name)
61 self._set_cache(backend.repo_name)
62
62
63 id_, params = build_data(
63 id_, params = build_data(
64 self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name)
64 self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name)
65 response = api_call(self.app, params)
65 response = api_call(self.app, params)
66
66
67 expected = "repository `%s` does not exist" % (backend.repo_name,)
67 expected = "repository `%s` does not exist" % (backend.repo_name,)
68 assert_error(id_, expected, given=response.body)
68 assert_error(id_, expected, given=response.body)
@@ -1,137 +1,137 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import UserLog, PullRequest
23 from rhodecode.model.db import UserLog, PullRequest
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestMergePullRequest(object):
31 class TestMergePullRequest(object):
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 pull_request = pr_util.create_pull_request(mergeable=True)
34 pull_request = pr_util.create_pull_request(mergeable=True)
35 author = pull_request.user_id
35 author = pull_request.user_id
36 repo = pull_request.target_repo.repo_id
36 repo = pull_request.target_repo.repo_id
37 pull_request_id = pull_request.pull_request_id
37 pull_request_id = pull_request.pull_request_id
38 pull_request_repo = pull_request.target_repo.repo_name
38 pull_request_repo = pull_request.target_repo.repo_name
39
39
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'merge_pull_request',
41 self.apikey, 'merge_pull_request',
42 repoid=pull_request_repo,
42 repoid=pull_request_repo,
43 pullrequestid=pull_request_id)
43 pullrequestid=pull_request_id)
44
44
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 # The above api call detaches the pull request DB object from the
47 # The above api call detaches the pull request DB object from the
48 # session because of an unconditional transaction rollback in our
48 # session because of an unconditional transaction rollback in our
49 # middleware. Therefore we need to add it back here if we want to use
49 # middleware. Therefore we need to add it back here if we want to use
50 # it.
50 # it.
51 Session().add(pull_request)
51 Session().add(pull_request)
52
52
53 expected = 'merge not possible for following reasons: ' \
53 expected = 'merge not possible for following reasons: ' \
54 'Pull request reviewer approval is pending.'
54 'Pull request reviewer approval is pending.'
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
56
56
57 @pytest.mark.backends("git", "hg")
57 @pytest.mark.backends("git", "hg")
58 def test_api_merge_pull_request(self, pr_util, no_notifications):
58 def test_api_merge_pull_request(self, pr_util, no_notifications):
59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
60 author = pull_request.user_id
60 author = pull_request.user_id
61 repo = pull_request.target_repo.repo_id
61 repo = pull_request.target_repo.repo_id
62 pull_request_id = pull_request.pull_request_id
62 pull_request_id = pull_request.pull_request_id
63 pull_request_repo = pull_request.target_repo.repo_name
63 pull_request_repo = pull_request.target_repo.repo_name
64
64
65 id_, params = build_data(
65 id_, params = build_data(
66 self.apikey, 'comment_pull_request',
66 self.apikey, 'comment_pull_request',
67 repoid=pull_request_repo,
67 repoid=pull_request_repo,
68 pullrequestid=pull_request_id,
68 pullrequestid=pull_request_id,
69 status='approved')
69 status='approved')
70
70
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72 expected = {
72 expected = {
73 'comment_id': response.json.get('result', {}).get('comment_id'),
73 'comment_id': response.json.get('result', {}).get('comment_id'),
74 'pull_request_id': pull_request_id,
74 'pull_request_id': pull_request_id,
75 'status': {'given': 'approved', 'was_changed': True}
75 'status': {'given': 'approved', 'was_changed': True}
76 }
76 }
77 assert_ok(id_, expected, given=response.body)
77 assert_ok(id_, expected, given=response.body)
78
78
79 id_, params = build_data(
79 id_, params = build_data(
80 self.apikey, 'merge_pull_request',
80 self.apikey, 'merge_pull_request',
81 repoid=pull_request_repo,
81 repoid=pull_request_repo,
82 pullrequestid=pull_request_id)
82 pullrequestid=pull_request_id)
83
83
84 response = api_call(self.app, params)
84 response = api_call(self.app, params)
85
85
86 pull_request = PullRequest.get(pull_request_id)
86 pull_request = PullRequest.get(pull_request_id)
87
87
88 expected = {
88 expected = {
89 'executed': True,
89 'executed': True,
90 'failure_reason': 0,
90 'failure_reason': 0,
91 'possible': True,
91 'possible': True,
92 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
92 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
93 'merge_ref': pull_request.shadow_merge_ref._asdict()
93 'merge_ref': pull_request.shadow_merge_ref._asdict()
94 }
94 }
95
95
96 assert_ok(id_, expected, response.body)
96 assert_ok(id_, expected, response.body)
97
97
98 journal = UserLog.query()\
98 journal = UserLog.query()\
99 .filter(UserLog.user_id == author)\
99 .filter(UserLog.user_id == author)\
100 .filter(UserLog.repository_id == repo) \
100 .filter(UserLog.repository_id == repo) \
101 .order_by('user_log_id') \
101 .order_by('user_log_id') \
102 .all()
102 .all()
103 assert journal[-2].action == 'repo.pull_request.merge'
103 assert journal[-2].action == 'repo.pull_request.merge'
104 assert journal[-1].action == 'repo.pull_request.close'
104 assert journal[-1].action == 'repo.pull_request.close'
105
105
106 id_, params = build_data(
106 id_, params = build_data(
107 self.apikey, 'merge_pull_request',
107 self.apikey, 'merge_pull_request',
108 repoid=pull_request_repo, pullrequestid=pull_request_id)
108 repoid=pull_request_repo, pullrequestid=pull_request_id)
109 response = api_call(self.app, params)
109 response = api_call(self.app, params)
110
110
111 expected = 'merge not possible for following reasons: This pull request is closed.'
111 expected = 'merge not possible for following reasons: This pull request is closed.'
112 assert_error(id_, expected, given=response.body)
112 assert_error(id_, expected, given=response.body)
113
113
114 @pytest.mark.backends("git", "hg")
114 @pytest.mark.backends("git", "hg")
115 def test_api_merge_pull_request_repo_error(self, pr_util):
115 def test_api_merge_pull_request_repo_error(self, pr_util):
116 pull_request = pr_util.create_pull_request()
116 pull_request = pr_util.create_pull_request()
117 id_, params = build_data(
117 id_, params = build_data(
118 self.apikey, 'merge_pull_request',
118 self.apikey, 'merge_pull_request',
119 repoid=666, pullrequestid=pull_request.pull_request_id)
119 repoid=666, pullrequestid=pull_request.pull_request_id)
120 response = api_call(self.app, params)
120 response = api_call(self.app, params)
121
121
122 expected = 'repository `666` does not exist'
122 expected = 'repository `666` does not exist'
123 assert_error(id_, expected, given=response.body)
123 assert_error(id_, expected, given=response.body)
124
124
125 @pytest.mark.backends("git", "hg")
125 @pytest.mark.backends("git", "hg")
126 def test_api_merge_pull_request_non_admin_with_userid_error(self,
126 def test_api_merge_pull_request_non_admin_with_userid_error(self,
127 pr_util):
127 pr_util):
128 pull_request = pr_util.create_pull_request(mergeable=True)
128 pull_request = pr_util.create_pull_request(mergeable=True)
129 id_, params = build_data(
129 id_, params = build_data(
130 self.apikey_regular, 'merge_pull_request',
130 self.apikey_regular, 'merge_pull_request',
131 repoid=pull_request.target_repo.repo_name,
131 repoid=pull_request.target_repo.repo_name,
132 pullrequestid=pull_request.pull_request_id,
132 pullrequestid=pull_request.pull_request_id,
133 userid=TEST_USER_ADMIN_LOGIN)
133 userid=TEST_USER_ADMIN_LOGIN)
134 response = api_call(self.app, params)
134 response = api_call(self.app, params)
135
135
136 expected = 'userid is not the same as your user'
136 expected = 'userid is not the same as your user'
137 assert_error(id_, expected, given=response.body)
137 assert_error(id_, expected, given=response.body)
@@ -1,51 +1,51 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.tests import TESTS_TMP_PATH
25 from rhodecode.tests import TESTS_TMP_PATH
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error)
27 build_data, api_call, assert_ok, assert_error)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestPull(object):
31 class TestPull(object):
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_pull(self, backend):
33 def test_api_pull(self, backend):
34 r = backend.create_repo()
34 r = backend.create_repo()
35 repo_name = r.repo_name
35 repo_name = r.repo_name
36 r.clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
36 r.clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
37
37
38 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
38 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40
40
41 expected = {'msg': 'Pulled from `%s`' % (repo_name,),
41 expected = {'msg': 'Pulled from `%s`' % (repo_name,),
42 'repository': repo_name}
42 'repository': repo_name}
43 assert_ok(id_, expected, given=response.body)
43 assert_ok(id_, expected, given=response.body)
44
44
45 def test_api_pull_error(self, backend):
45 def test_api_pull_error(self, backend):
46 id_, params = build_data(
46 id_, params = build_data(
47 self.apikey, 'pull', repoid=backend.repo_name)
47 self.apikey, 'pull', repoid=backend.repo_name)
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = 'Unable to pull changes from `%s`' % (backend.repo_name,)
50 expected = 'Unable to pull changes from `%s`' % (backend.repo_name,)
51 assert_error(id_, expected, given=response.body)
51 assert_error(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import Repository, RepositoryField
23 from rhodecode.model.db import Repository, RepositoryField
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestRemoveFieldFromRepo(object):
28 class TestRemoveFieldFromRepo(object):
29 def test_api_remove_field_from_repo(self, backend):
29 def test_api_remove_field_from_repo(self, backend):
30 repo = backend.create_repo()
30 repo = backend.create_repo()
31 repo_name = repo.repo_name
31 repo_name = repo.repo_name
32
32
33 id_, params = build_data(
33 id_, params = build_data(
34 self.apikey, 'add_field_to_repo',
34 self.apikey, 'add_field_to_repo',
35 repoid=repo_name,
35 repoid=repo_name,
36 key='extra_field',
36 key='extra_field',
37 label='extra_field_label',
37 label='extra_field_label',
38 description='extra_field_desc')
38 description='extra_field_desc')
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40 expected = {
40 expected = {
41 'msg': 'Added new repository field `extra_field`',
41 'msg': 'Added new repository field `extra_field`',
42 'success': True,
42 'success': True,
43 }
43 }
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 repo = Repository.get_by_repo_name(repo_name)
46 repo = Repository.get_by_repo_name(repo_name)
47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
48 _data = repo_field.get_dict()
48 _data = repo_field.get_dict()
49 assert _data['field_desc'] == 'extra_field_desc'
49 assert _data['field_desc'] == 'extra_field_desc'
50 assert _data['field_key'] == 'extra_field'
50 assert _data['field_key'] == 'extra_field'
51 assert _data['field_label'] == 'extra_field_label'
51 assert _data['field_label'] == 'extra_field_label'
52
52
53 id_, params = build_data(
53 id_, params = build_data(
54 self.apikey, 'remove_field_from_repo',
54 self.apikey, 'remove_field_from_repo',
55 repoid=repo_name,
55 repoid=repo_name,
56 key='extra_field')
56 key='extra_field')
57 response = api_call(self.app, params)
57 response = api_call(self.app, params)
58 expected = {
58 expected = {
59 'msg': 'Deleted repository field `extra_field`',
59 'msg': 'Deleted repository field `extra_field`',
60 'success': True,
60 'success': True,
61 }
61 }
62 assert_ok(id_, expected, given=response.body)
62 assert_ok(id_, expected, given=response.body)
63 repo = Repository.get_by_repo_name(repo_name)
63 repo = Repository.get_by_repo_name(repo_name)
64 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
64 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
65
65
66 assert repo_field is None
66 assert repo_field is None
@@ -1,58 +1,58 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRemoveUserFromUserGroup(object):
30 class TestRemoveUserFromUserGroup(object):
31 def test_api_remove_user_from_user_group(self, user_util):
31 def test_api_remove_user_from_user_group(self, user_util):
32 user, group = user_util.create_user_with_group()
32 user, group = user_util.create_user_with_group()
33 user_name = user.username
33 user_name = user.username
34 group_name = group.users_group_name
34 group_name = group.users_group_name
35 id_, params = build_data(
35 id_, params = build_data(
36 self.apikey, 'remove_user_from_user_group',
36 self.apikey, 'remove_user_from_user_group',
37 usergroupid=group_name,
37 usergroupid=group_name,
38 userid=user.username)
38 userid=user.username)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40
40
41 expected = {
41 expected = {
42 'msg': 'removed member `%s` from user group `%s`' % (
42 'msg': 'removed member `%s` from user group `%s`' % (
43 user_name, group_name
43 user_name, group_name
44 ),
44 ),
45 'success': True}
45 'success': True}
46 assert_ok(id_, expected, given=response.body)
46 assert_ok(id_, expected, given=response.body)
47
47
48 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
48 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
49 def test_api_remove_user_from_user_group_exception_occurred(
49 def test_api_remove_user_from_user_group_exception_occurred(
50 self, user_util):
50 self, user_util):
51 user, group = user_util.create_user_with_group()
51 user, group = user_util.create_user_with_group()
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'remove_user_from_user_group',
53 self.apikey, 'remove_user_from_user_group',
54 usergroupid=group.users_group_name, userid=user.username)
54 usergroupid=group.users_group_name, userid=user.username)
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56 expected = 'failed to remove member from user group `%s`' % (
56 expected = 'failed to remove member from user group `%s`' % (
57 group.users_group_name)
57 group.users_group_name)
58 assert_error(id_, expected, given=response.body)
58 assert_error(id_, expected, given=response.body)
@@ -1,184 +1,184 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.db import Repository
25 from rhodecode.model.db import Repository
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.lib.ext_json import json
27 from rhodecode.lib.ext_json import json
28 from rhodecode.lib.utils2 import time_to_datetime
28 from rhodecode.lib.utils2 import time_to_datetime
29 from rhodecode.api.tests.utils import (
29 from rhodecode.api.tests.utils import (
30 build_data, api_call, assert_ok, assert_error, crash)
30 build_data, api_call, assert_ok, assert_error, crash)
31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
32
32
33
33
34 @pytest.mark.usefixtures("testuser_api", "app")
34 @pytest.mark.usefixtures("testuser_api", "app")
35 class TestLock(object):
35 class TestLock(object):
36 def test_api_lock_repo_lock_aquire(self, backend):
36 def test_api_lock_repo_lock_aquire(self, backend):
37 id_, params = build_data(
37 id_, params = build_data(
38 self.apikey, 'lock',
38 self.apikey, 'lock',
39 userid=TEST_USER_ADMIN_LOGIN,
39 userid=TEST_USER_ADMIN_LOGIN,
40 repoid=backend.repo_name,
40 repoid=backend.repo_name,
41 locked=True)
41 locked=True)
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43 expected = {
43 expected = {
44 'repo': backend.repo_name, 'locked': True,
44 'repo': backend.repo_name, 'locked': True,
45 'locked_since': response.json['result']['locked_since'],
45 'locked_since': response.json['result']['locked_since'],
46 'locked_by': TEST_USER_ADMIN_LOGIN,
46 'locked_by': TEST_USER_ADMIN_LOGIN,
47 'lock_state_changed': True,
47 'lock_state_changed': True,
48 'lock_reason': Repository.LOCK_API,
48 'lock_reason': Repository.LOCK_API,
49 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
49 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
50 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
50 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
51 }
51 }
52 assert_ok(id_, expected, given=response.body)
52 assert_ok(id_, expected, given=response.body)
53
53
54 def test_repo_lock_aquire_by_non_admin(self, backend):
54 def test_repo_lock_aquire_by_non_admin(self, backend):
55 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
55 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
56 repo_name = repo.repo_name
56 repo_name = repo.repo_name
57 id_, params = build_data(
57 id_, params = build_data(
58 self.apikey_regular, 'lock',
58 self.apikey_regular, 'lock',
59 repoid=repo_name,
59 repoid=repo_name,
60 locked=True)
60 locked=True)
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62 expected = {
62 expected = {
63 'repo': repo_name,
63 'repo': repo_name,
64 'locked': True,
64 'locked': True,
65 'locked_since': response.json['result']['locked_since'],
65 'locked_since': response.json['result']['locked_since'],
66 'locked_by': self.TEST_USER_LOGIN,
66 'locked_by': self.TEST_USER_LOGIN,
67 'lock_state_changed': True,
67 'lock_state_changed': True,
68 'lock_reason': Repository.LOCK_API,
68 'lock_reason': Repository.LOCK_API,
69 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
69 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
70 % (self.TEST_USER_LOGIN, repo_name, True))
70 % (self.TEST_USER_LOGIN, repo_name, True))
71 }
71 }
72 assert_ok(id_, expected, given=response.body)
72 assert_ok(id_, expected, given=response.body)
73
73
74 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend):
74 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend):
75 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
75 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
76 repo_name = repo.repo_name
76 repo_name = repo.repo_name
77 id_, params = build_data(
77 id_, params = build_data(
78 self.apikey_regular, 'lock',
78 self.apikey_regular, 'lock',
79 userid=TEST_USER_ADMIN_LOGIN,
79 userid=TEST_USER_ADMIN_LOGIN,
80 repoid=repo_name,
80 repoid=repo_name,
81 locked=True)
81 locked=True)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 expected = 'userid is not the same as your user'
83 expected = 'userid is not the same as your user'
84 assert_error(id_, expected, given=response.body)
84 assert_error(id_, expected, given=response.body)
85
85
86 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend):
86 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend):
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'lock',
88 self.apikey_regular, 'lock',
89 repoid=backend.repo_name,
89 repoid=backend.repo_name,
90 locked=True)
90 locked=True)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92 expected = 'repository `%s` does not exist' % (backend.repo_name, )
92 expected = 'repository `%s` does not exist' % (backend.repo_name, )
93 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
94
94
95 def test_api_lock_repo_lock_release(self, backend):
95 def test_api_lock_repo_lock_release(self, backend):
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey, 'lock',
97 self.apikey, 'lock',
98 userid=TEST_USER_ADMIN_LOGIN,
98 userid=TEST_USER_ADMIN_LOGIN,
99 repoid=backend.repo_name,
99 repoid=backend.repo_name,
100 locked=False)
100 locked=False)
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102 expected = {
102 expected = {
103 'repo': backend.repo_name,
103 'repo': backend.repo_name,
104 'locked': False,
104 'locked': False,
105 'locked_since': None,
105 'locked_since': None,
106 'locked_by': TEST_USER_ADMIN_LOGIN,
106 'locked_by': TEST_USER_ADMIN_LOGIN,
107 'lock_state_changed': True,
107 'lock_state_changed': True,
108 'lock_reason': Repository.LOCK_API,
108 'lock_reason': Repository.LOCK_API,
109 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
109 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
110 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False))
110 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False))
111 }
111 }
112 assert_ok(id_, expected, given=response.body)
112 assert_ok(id_, expected, given=response.body)
113
113
114 def test_api_lock_repo_lock_aquire_optional_userid(self, backend):
114 def test_api_lock_repo_lock_aquire_optional_userid(self, backend):
115 id_, params = build_data(
115 id_, params = build_data(
116 self.apikey, 'lock',
116 self.apikey, 'lock',
117 repoid=backend.repo_name,
117 repoid=backend.repo_name,
118 locked=True)
118 locked=True)
119 response = api_call(self.app, params)
119 response = api_call(self.app, params)
120 time_ = response.json['result']['locked_since']
120 time_ = response.json['result']['locked_since']
121 expected = {
121 expected = {
122 'repo': backend.repo_name,
122 'repo': backend.repo_name,
123 'locked': True,
123 'locked': True,
124 'locked_since': time_,
124 'locked_since': time_,
125 'locked_by': TEST_USER_ADMIN_LOGIN,
125 'locked_by': TEST_USER_ADMIN_LOGIN,
126 'lock_state_changed': True,
126 'lock_state_changed': True,
127 'lock_reason': Repository.LOCK_API,
127 'lock_reason': Repository.LOCK_API,
128 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
128 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
129 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
129 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
130 }
130 }
131
131
132 assert_ok(id_, expected, given=response.body)
132 assert_ok(id_, expected, given=response.body)
133
133
134 def test_api_lock_repo_lock_optional_locked(self, backend):
134 def test_api_lock_repo_lock_optional_locked(self, backend):
135 # TODO: Provide a fixture locked_repository or similar
135 # TODO: Provide a fixture locked_repository or similar
136 repo = Repository.get_by_repo_name(backend.repo_name)
136 repo = Repository.get_by_repo_name(backend.repo_name)
137 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
137 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
138 Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API)
138 Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API)
139
139
140 id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name)
140 id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name)
141 response = api_call(self.app, params)
141 response = api_call(self.app, params)
142 time_ = response.json['result']['locked_since']
142 time_ = response.json['result']['locked_since']
143 expected = {
143 expected = {
144 'repo': backend.repo_name,
144 'repo': backend.repo_name,
145 'locked': True,
145 'locked': True,
146 'locked_since': time_,
146 'locked_since': time_,
147 'locked_by': TEST_USER_ADMIN_LOGIN,
147 'locked_by': TEST_USER_ADMIN_LOGIN,
148 'lock_state_changed': False,
148 'lock_state_changed': False,
149 'lock_reason': Repository.LOCK_API,
149 'lock_reason': Repository.LOCK_API,
150 'msg': ('Repo `%s` locked by `%s` on `%s`.'
150 'msg': ('Repo `%s` locked by `%s` on `%s`.'
151 % (backend.repo_name, TEST_USER_ADMIN_LOGIN,
151 % (backend.repo_name, TEST_USER_ADMIN_LOGIN,
152 json.dumps(time_to_datetime(time_))))
152 json.dumps(time_to_datetime(time_))))
153 }
153 }
154 assert_ok(id_, expected, given=response.body)
154 assert_ok(id_, expected, given=response.body)
155
155
156 def test_api_lock_repo_lock_optional_not_locked(self, backend):
156 def test_api_lock_repo_lock_optional_not_locked(self, backend):
157 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
157 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
158 repo_name = repo.repo_name
158 repo_name = repo.repo_name
159 assert repo.locked == [None, None, None]
159 assert repo.locked == [None, None, None]
160 id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id)
160 id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id)
161 response = api_call(self.app, params)
161 response = api_call(self.app, params)
162 expected = {
162 expected = {
163 'repo': repo_name,
163 'repo': repo_name,
164 'locked': False,
164 'locked': False,
165 'locked_since': None,
165 'locked_since': None,
166 'locked_by': None,
166 'locked_by': None,
167 'lock_state_changed': False,
167 'lock_state_changed': False,
168 'lock_reason': None,
168 'lock_reason': None,
169 'msg': ('Repo `%s` not locked.' % (repo_name,))
169 'msg': ('Repo `%s` not locked.' % (repo_name,))
170 }
170 }
171 assert_ok(id_, expected, given=response.body)
171 assert_ok(id_, expected, given=response.body)
172
172
173 @mock.patch.object(Repository, 'lock', crash)
173 @mock.patch.object(Repository, 'lock', crash)
174 def test_api_lock_error(self, backend):
174 def test_api_lock_error(self, backend):
175 id_, params = build_data(
175 id_, params = build_data(
176 self.apikey, 'lock',
176 self.apikey, 'lock',
177 userid=TEST_USER_ADMIN_LOGIN,
177 userid=TEST_USER_ADMIN_LOGIN,
178 repoid=backend.repo_name,
178 repoid=backend.repo_name,
179 locked=True)
179 locked=True)
180 response = api_call(self.app, params)
180 response = api_call(self.app, params)
181
181
182 expected = 'Error occurred locking repository `%s`' % (
182 expected = 'Error occurred locking repository `%s`' % (
183 backend.repo_name,)
183 backend.repo_name,)
184 assert_error(id_, expected, given=response.body)
184 assert_error(id_, expected, given=response.body)
@@ -1,44 +1,44 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.scm import ScmModel
24 from rhodecode.model.scm import ScmModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, crash)
26 build_data, api_call, assert_ok, assert_error, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRescanRepos(object):
30 class TestRescanRepos(object):
31 def test_api_rescan_repos(self):
31 def test_api_rescan_repos(self):
32 id_, params = build_data(self.apikey, 'rescan_repos')
32 id_, params = build_data(self.apikey, 'rescan_repos')
33 response = api_call(self.app, params)
33 response = api_call(self.app, params)
34
34
35 expected = {'added': [], 'removed': []}
35 expected = {'added': [], 'removed': []}
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
37
37
38 @mock.patch.object(ScmModel, 'repo_scan', crash)
38 @mock.patch.object(ScmModel, 'repo_scan', crash)
39 def test_api_rescann_error(self):
39 def test_api_rescann_error(self):
40 id_, params = build_data(self.apikey, 'rescan_repos', )
40 id_, params = build_data(self.apikey, 'rescan_repos', )
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = 'Error occurred during rescan repositories action'
43 expected = 'Error occurred during rescan repositories action'
44 assert_error(id_, expected, given=response.body)
44 assert_error(id_, expected, given=response.body)
@@ -1,67 +1,67 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRevokeUserGroupPermission(object):
30 class TestRevokeUserGroupPermission(object):
31 def test_api_revoke_user_group_permission(self, backend, user_util):
31 def test_api_revoke_user_group_permission(self, backend, user_util):
32 repo = backend.create_repo()
32 repo = backend.create_repo()
33 user_group = user_util.create_user_group()
33 user_group = user_util.create_user_group()
34 user_util.grant_user_group_permission_to_repo(
34 user_util.grant_user_group_permission_to_repo(
35 repo, user_group, 'repository.read')
35 repo, user_group, 'repository.read')
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey,
37 self.apikey,
38 'revoke_user_group_permission',
38 'revoke_user_group_permission',
39 repoid=backend.repo_name,
39 repoid=backend.repo_name,
40 usergroupid=user_group.users_group_name)
40 usergroupid=user_group.users_group_name)
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = {
43 expected = {
44 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
44 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
45 user_group.users_group_name, backend.repo_name
45 user_group.users_group_name, backend.repo_name
46 ),
46 ),
47 'success': True
47 'success': True
48 }
48 }
49 assert_ok(id_, expected, given=response.body)
49 assert_ok(id_, expected, given=response.body)
50
50
51 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
51 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
52 def test_api_revoke_user_group_permission_exception_when_adding(
52 def test_api_revoke_user_group_permission_exception_when_adding(
53 self, backend, user_util):
53 self, backend, user_util):
54 user_group = user_util.create_user_group()
54 user_group = user_util.create_user_group()
55 id_, params = build_data(
55 id_, params = build_data(
56 self.apikey,
56 self.apikey,
57 'revoke_user_group_permission',
57 'revoke_user_group_permission',
58 repoid=backend.repo_name,
58 repoid=backend.repo_name,
59 usergroupid=user_group.users_group_name)
59 usergroupid=user_group.users_group_name)
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61
61
62 expected = (
62 expected = (
63 'failed to edit permission for user group: `%s` in repo: `%s`' % (
63 'failed to edit permission for user group: `%s` in repo: `%s`' % (
64 user_group.users_group_name, backend.repo_name
64 user_group.users_group_name, backend.repo_name
65 )
65 )
66 )
66 )
67 assert_error(id_, expected, given=response.body)
67 assert_error(id_, expected, given=response.body)
@@ -1,129 +1,129 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestRevokeUserGroupPermissionFromRepoGroup(object):
31 class TestRevokeUserGroupPermissionFromRepoGroup(object):
32 @pytest.mark.parametrize("name, apply_to_children", [
32 @pytest.mark.parametrize("name, apply_to_children", [
33 ('none', 'none'),
33 ('none', 'none'),
34 ('all', 'all'),
34 ('all', 'all'),
35 ('repos', 'repos'),
35 ('repos', 'repos'),
36 ('groups', 'groups'),
36 ('groups', 'groups'),
37 ])
37 ])
38 def test_api_revoke_user_group_permission_from_repo_group(
38 def test_api_revoke_user_group_permission_from_repo_group(
39 self, name, apply_to_children, user_util):
39 self, name, apply_to_children, user_util):
40 user_group = user_util.create_user_group()
40 user_group = user_util.create_user_group()
41 repo_group = user_util.create_repo_group()
41 repo_group = user_util.create_repo_group()
42 user_util.grant_user_group_permission_to_repo_group(
42 user_util.grant_user_group_permission_to_repo_group(
43 repo_group, user_group, 'group.read')
43 repo_group, user_group, 'group.read')
44
44
45 id_, params = build_data(
45 id_, params = build_data(
46 self.apikey, 'revoke_user_group_permission_from_repo_group',
46 self.apikey, 'revoke_user_group_permission_from_repo_group',
47 repogroupid=repo_group.name,
47 repogroupid=repo_group.name,
48 usergroupid=user_group.users_group_name,
48 usergroupid=user_group.users_group_name,
49 apply_to_children=apply_to_children,)
49 apply_to_children=apply_to_children,)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 expected = {
52 expected = {
53 'msg': (
53 'msg': (
54 'Revoked perm (recursive:%s) for user group: `%s`'
54 'Revoked perm (recursive:%s) for user group: `%s`'
55 ' in repo group: `%s`' % (
55 ' in repo group: `%s`' % (
56 apply_to_children, user_group.users_group_name,
56 apply_to_children, user_group.users_group_name,
57 repo_group.name
57 repo_group.name
58 )
58 )
59 ),
59 ),
60 'success': True
60 'success': True
61 }
61 }
62 assert_ok(id_, expected, given=response.body)
62 assert_ok(id_, expected, given=response.body)
63
63
64 @pytest.mark.parametrize(
64 @pytest.mark.parametrize(
65 "name, apply_to_children, grant_admin, access_ok", [
65 "name, apply_to_children, grant_admin, access_ok", [
66 ('none', 'none', False, False),
66 ('none', 'none', False, False),
67 ('all', 'all', False, False),
67 ('all', 'all', False, False),
68 ('repos', 'repos', False, False),
68 ('repos', 'repos', False, False),
69 ('groups', 'groups', False, False),
69 ('groups', 'groups', False, False),
70
70
71 # after granting admin rights
71 # after granting admin rights
72 ('none', 'none', False, False),
72 ('none', 'none', False, False),
73 ('all', 'all', False, False),
73 ('all', 'all', False, False),
74 ('repos', 'repos', False, False),
74 ('repos', 'repos', False, False),
75 ('groups', 'groups', False, False),
75 ('groups', 'groups', False, False),
76 ]
76 ]
77 )
77 )
78 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
78 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
79 self, name, apply_to_children, grant_admin, access_ok, user_util):
79 self, name, apply_to_children, grant_admin, access_ok, user_util):
80 user_group = user_util.create_user_group()
80 user_group = user_util.create_user_group()
81 repo_group = user_util.create_repo_group()
81 repo_group = user_util.create_repo_group()
82 user_util.grant_user_group_permission_to_repo_group(
82 user_util.grant_user_group_permission_to_repo_group(
83 repo_group, user_group, 'group.read')
83 repo_group, user_group, 'group.read')
84
84
85 if grant_admin:
85 if grant_admin:
86 user_util.grant_user_permission_to_repo_group(
86 user_util.grant_user_permission_to_repo_group(
87 repo_group.name, self.TEST_USER_LOGIN, 'group.admin')
87 repo_group.name, self.TEST_USER_LOGIN, 'group.admin')
88
88
89 id_, params = build_data(
89 id_, params = build_data(
90 self.apikey_regular,
90 self.apikey_regular,
91 'revoke_user_group_permission_from_repo_group',
91 'revoke_user_group_permission_from_repo_group',
92 repogroupid=repo_group.name,
92 repogroupid=repo_group.name,
93 usergroupid=user_group.users_group_name,
93 usergroupid=user_group.users_group_name,
94 apply_to_children=apply_to_children,)
94 apply_to_children=apply_to_children,)
95 response = api_call(self.app, params)
95 response = api_call(self.app, params)
96 if access_ok:
96 if access_ok:
97 expected = {
97 expected = {
98 'msg': (
98 'msg': (
99 'Revoked perm (recursive:%s) for user group: `%s`'
99 'Revoked perm (recursive:%s) for user group: `%s`'
100 ' in repo group: `%s`' % (
100 ' in repo group: `%s`' % (
101 apply_to_children, TEST_USER_ADMIN_LOGIN,
101 apply_to_children, TEST_USER_ADMIN_LOGIN,
102 repo_group.name
102 repo_group.name
103 )
103 )
104 ),
104 ),
105 'success': True
105 'success': True
106 }
106 }
107 assert_ok(id_, expected, given=response.body)
107 assert_ok(id_, expected, given=response.body)
108 else:
108 else:
109 expected = 'repository group `%s` does not exist' % (
109 expected = 'repository group `%s` does not exist' % (
110 repo_group.name,)
110 repo_group.name,)
111 assert_error(id_, expected, given=response.body)
111 assert_error(id_, expected, given=response.body)
112
112
113 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
113 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
114 def test_api_revoke_user_group_permission_from_repo_group_exception_on_add(
114 def test_api_revoke_user_group_permission_from_repo_group_exception_on_add(
115 self, user_util):
115 self, user_util):
116 user_group = user_util.create_user_group()
116 user_group = user_util.create_user_group()
117 repo_group = user_util.create_repo_group()
117 repo_group = user_util.create_repo_group()
118 id_, params = build_data(
118 id_, params = build_data(
119 self.apikey, 'revoke_user_group_permission_from_repo_group',
119 self.apikey, 'revoke_user_group_permission_from_repo_group',
120 repogroupid=repo_group.name,
120 repogroupid=repo_group.name,
121 usergroupid=user_group.users_group_name)
121 usergroupid=user_group.users_group_name)
122 response = api_call(self.app, params)
122 response = api_call(self.app, params)
123
123
124 expected = (
124 expected = (
125 'failed to edit permission for user group: `%s`'
125 'failed to edit permission for user group: `%s`'
126 ' in repo group: `%s`' % (
126 ' in repo group: `%s`' % (
127 user_group.users_group_name, repo_group.name)
127 user_group.users_group_name, repo_group.name)
128 )
128 )
129 assert_error(id_, expected, given=response.body)
129 assert_error(id_, expected, given=response.body)
@@ -1,58 +1,58 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestRevokeUserGroupPermissionFromUserGroup(object):
28 class TestRevokeUserGroupPermissionFromUserGroup(object):
29 @pytest.mark.parametrize("name", [
29 @pytest.mark.parametrize("name", [
30 ('none',),
30 ('none',),
31 ('all',),
31 ('all',),
32 ('repos',),
32 ('repos',),
33 ('groups',),
33 ('groups',),
34 ])
34 ])
35 def test_api_revoke_user_group_permission_from_user_group(
35 def test_api_revoke_user_group_permission_from_user_group(
36 self, name, user_util):
36 self, name, user_util):
37 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
37 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
38 group = user_util.create_user_group()
38 group = user_util.create_user_group()
39 source_group = user_util.create_user_group()
39 source_group = user_util.create_user_group()
40
40
41 user_util.grant_user_permission_to_user_group(
41 user_util.grant_user_permission_to_user_group(
42 group, user, 'usergroup.read')
42 group, user, 'usergroup.read')
43 user_util.grant_user_group_permission_to_user_group(
43 user_util.grant_user_group_permission_to_user_group(
44 source_group, group, 'usergroup.read')
44 source_group, group, 'usergroup.read')
45
45
46 id_, params = build_data(
46 id_, params = build_data(
47 self.apikey, 'revoke_user_group_permission_from_user_group',
47 self.apikey, 'revoke_user_group_permission_from_user_group',
48 usergroupid=group.users_group_name,
48 usergroupid=group.users_group_name,
49 sourceusergroupid=source_group.users_group_name)
49 sourceusergroupid=source_group.users_group_name)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 expected = {
52 expected = {
53 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % (
53 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % (
54 source_group.users_group_name, group.users_group_name
54 source_group.users_group_name, group.users_group_name
55 ),
55 ),
56 'success': True
56 'success': True
57 }
57 }
58 assert_ok(id_, expected, given=response.body)
58 assert_ok(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRevokeUserPermission(object):
30 class TestRevokeUserPermission(object):
31 def test_api_revoke_user_permission(self, backend, user_util):
31 def test_api_revoke_user_permission(self, backend, user_util):
32 repo = backend.create_repo()
32 repo = backend.create_repo()
33 user = user_util.create_user()
33 user = user_util.create_user()
34 user_util.grant_user_permission_to_repo(
34 user_util.grant_user_permission_to_repo(
35 repo, user, 'repository.read')
35 repo, user, 'repository.read')
36
36
37 id_, params = build_data(
37 id_, params = build_data(
38 self.apikey,
38 self.apikey,
39 'revoke_user_permission',
39 'revoke_user_permission',
40 repoid=repo.repo_name,
40 repoid=repo.repo_name,
41 userid=user.username)
41 userid=user.username)
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43
43
44 expected = {
44 expected = {
45 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
45 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
46 user.username, backend.repo_name
46 user.username, backend.repo_name
47 ),
47 ),
48 'success': True
48 'success': True
49 }
49 }
50 assert_ok(id_, expected, given=response.body)
50 assert_ok(id_, expected, given=response.body)
51
51
52 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
52 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
53 def test_api_revoke_user_permission_exception_when_adding(
53 def test_api_revoke_user_permission_exception_when_adding(
54 self, backend, user_util):
54 self, backend, user_util):
55 user = user_util.create_user()
55 user = user_util.create_user()
56 id_, params = build_data(
56 id_, params = build_data(
57 self.apikey,
57 self.apikey,
58 'revoke_user_permission',
58 'revoke_user_permission',
59 repoid=backend.repo_name,
59 repoid=backend.repo_name,
60 userid=user.username)
60 userid=user.username)
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62
62
63 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
63 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
64 user.username, backend.repo_name
64 user.username, backend.repo_name
65 )
65 )
66 assert_error(id_, expected, given=response.body)
66 assert_error(id_, expected, given=response.body)
@@ -1,126 +1,126 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRevokeUserPermissionFromRepoGroup(object):
30 class TestRevokeUserPermissionFromRepoGroup(object):
31 @pytest.mark.parametrize("name, apply_to_children", [
31 @pytest.mark.parametrize("name, apply_to_children", [
32 ('none', 'none'),
32 ('none', 'none'),
33 ('all', 'all'),
33 ('all', 'all'),
34 ('repos', 'repos'),
34 ('repos', 'repos'),
35 ('groups', 'groups'),
35 ('groups', 'groups'),
36 ])
36 ])
37 def test_api_revoke_user_permission_from_repo_group(
37 def test_api_revoke_user_permission_from_repo_group(
38 self, name, apply_to_children, user_util):
38 self, name, apply_to_children, user_util):
39 user = user_util.create_user()
39 user = user_util.create_user()
40 repo_group = user_util.create_repo_group()
40 repo_group = user_util.create_repo_group()
41 user_util.grant_user_permission_to_repo_group(
41 user_util.grant_user_permission_to_repo_group(
42 repo_group, user, 'group.read')
42 repo_group, user, 'group.read')
43
43
44 id_, params = build_data(
44 id_, params = build_data(
45 self.apikey,
45 self.apikey,
46 'revoke_user_permission_from_repo_group',
46 'revoke_user_permission_from_repo_group',
47 repogroupid=repo_group.name,
47 repogroupid=repo_group.name,
48 userid=user.username,
48 userid=user.username,
49 apply_to_children=apply_to_children,)
49 apply_to_children=apply_to_children,)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 expected = {
52 expected = {
53 'msg': (
53 'msg': (
54 'Revoked perm (recursive:%s) for user: `%s`'
54 'Revoked perm (recursive:%s) for user: `%s`'
55 ' in repo group: `%s`' % (
55 ' in repo group: `%s`' % (
56 apply_to_children, user.username, repo_group.name
56 apply_to_children, user.username, repo_group.name
57 )
57 )
58 ),
58 ),
59 'success': True
59 'success': True
60 }
60 }
61 assert_ok(id_, expected, given=response.body)
61 assert_ok(id_, expected, given=response.body)
62
62
63 @pytest.mark.parametrize(
63 @pytest.mark.parametrize(
64 "name, apply_to_children, grant_admin, access_ok", [
64 "name, apply_to_children, grant_admin, access_ok", [
65 ('none', 'none', False, False),
65 ('none', 'none', False, False),
66 ('all', 'all', False, False),
66 ('all', 'all', False, False),
67 ('repos', 'repos', False, False),
67 ('repos', 'repos', False, False),
68 ('groups', 'groups', False, False),
68 ('groups', 'groups', False, False),
69
69
70 # after granting admin rights
70 # after granting admin rights
71 ('none', 'none', False, False),
71 ('none', 'none', False, False),
72 ('all', 'all', False, False),
72 ('all', 'all', False, False),
73 ('repos', 'repos', False, False),
73 ('repos', 'repos', False, False),
74 ('groups', 'groups', False, False),
74 ('groups', 'groups', False, False),
75 ]
75 ]
76 )
76 )
77 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
77 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
78 self, name, apply_to_children, grant_admin, access_ok, user_util):
78 self, name, apply_to_children, grant_admin, access_ok, user_util):
79 user = user_util.create_user()
79 user = user_util.create_user()
80 repo_group = user_util.create_repo_group()
80 repo_group = user_util.create_repo_group()
81 permission = 'group.admin' if grant_admin else 'group.read'
81 permission = 'group.admin' if grant_admin else 'group.read'
82 user_util.grant_user_permission_to_repo_group(
82 user_util.grant_user_permission_to_repo_group(
83 repo_group, user, permission)
83 repo_group, user, permission)
84
84
85 id_, params = build_data(
85 id_, params = build_data(
86 self.apikey_regular,
86 self.apikey_regular,
87 'revoke_user_permission_from_repo_group',
87 'revoke_user_permission_from_repo_group',
88 repogroupid=repo_group.name,
88 repogroupid=repo_group.name,
89 userid=user.username,
89 userid=user.username,
90 apply_to_children=apply_to_children,)
90 apply_to_children=apply_to_children,)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92 if access_ok:
92 if access_ok:
93 expected = {
93 expected = {
94 'msg': (
94 'msg': (
95 'Revoked perm (recursive:%s) for user: `%s`'
95 'Revoked perm (recursive:%s) for user: `%s`'
96 ' in repo group: `%s`' % (
96 ' in repo group: `%s`' % (
97 apply_to_children, user.username, repo_group.name
97 apply_to_children, user.username, repo_group.name
98 )
98 )
99 ),
99 ),
100 'success': True
100 'success': True
101 }
101 }
102 assert_ok(id_, expected, given=response.body)
102 assert_ok(id_, expected, given=response.body)
103 else:
103 else:
104 expected = 'repository group `%s` does not exist' % (
104 expected = 'repository group `%s` does not exist' % (
105 repo_group.name)
105 repo_group.name)
106 assert_error(id_, expected, given=response.body)
106 assert_error(id_, expected, given=response.body)
107
107
108 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
108 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
109 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(
109 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(
110 self, user_util):
110 self, user_util):
111 user = user_util.create_user()
111 user = user_util.create_user()
112 repo_group = user_util.create_repo_group()
112 repo_group = user_util.create_repo_group()
113 id_, params = build_data(
113 id_, params = build_data(
114 self.apikey,
114 self.apikey,
115 'revoke_user_permission_from_repo_group',
115 'revoke_user_permission_from_repo_group',
116 repogroupid=repo_group.name,
116 repogroupid=repo_group.name,
117 userid=user.username
117 userid=user.username
118 )
118 )
119 response = api_call(self.app, params)
119 response = api_call(self.app, params)
120
120
121 expected = (
121 expected = (
122 'failed to edit permission for user: `%s` in repo group: `%s`' % (
122 'failed to edit permission for user: `%s` in repo group: `%s`' % (
123 user.username, repo_group.name
123 user.username, repo_group.name
124 )
124 )
125 )
125 )
126 assert_error(id_, expected, given=response.body)
126 assert_error(id_, expected, given=response.body)
@@ -1,112 +1,112 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRevokeUserPermissionFromUserGroup(object):
30 class TestRevokeUserPermissionFromUserGroup(object):
31 @pytest.mark.parametrize("name", [
31 @pytest.mark.parametrize("name", [
32 ('none',),
32 ('none',),
33 ('all',),
33 ('all',),
34 ('repos',),
34 ('repos',),
35 ('groups',),
35 ('groups',),
36 ])
36 ])
37 def test_api_revoke_user_permission_from_user_group(self, name, user_util):
37 def test_api_revoke_user_permission_from_user_group(self, name, user_util):
38 user = user_util.create_user()
38 user = user_util.create_user()
39 group = user_util.create_user_group()
39 group = user_util.create_user_group()
40 user_util.grant_user_permission_to_user_group(
40 user_util.grant_user_permission_to_user_group(
41 group, user, 'usergroup.admin')
41 group, user, 'usergroup.admin')
42
42
43 id_, params = build_data(
43 id_, params = build_data(
44 self.apikey,
44 self.apikey,
45 'revoke_user_permission_from_user_group',
45 'revoke_user_permission_from_user_group',
46 usergroupid=group.users_group_name,
46 usergroupid=group.users_group_name,
47 userid=user.username)
47 userid=user.username)
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = {
50 expected = {
51 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
51 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
52 user.username, group.users_group_name
52 user.username, group.users_group_name
53 ),
53 ),
54 'success': True
54 'success': True
55 }
55 }
56 assert_ok(id_, expected, given=response.body)
56 assert_ok(id_, expected, given=response.body)
57
57
58 @pytest.mark.parametrize("name, grant_admin, access_ok", [
58 @pytest.mark.parametrize("name, grant_admin, access_ok", [
59 ('none', False, False),
59 ('none', False, False),
60 ('all', False, False),
60 ('all', False, False),
61 ('repos', False, False),
61 ('repos', False, False),
62 ('groups', False, False),
62 ('groups', False, False),
63
63
64 # after granting admin rights
64 # after granting admin rights
65 ('none', False, False),
65 ('none', False, False),
66 ('all', False, False),
66 ('all', False, False),
67 ('repos', False, False),
67 ('repos', False, False),
68 ('groups', False, False),
68 ('groups', False, False),
69 ])
69 ])
70 def test_api_revoke_user_permission_from_user_group_by_regular_user(
70 def test_api_revoke_user_permission_from_user_group_by_regular_user(
71 self, name, grant_admin, access_ok, user_util):
71 self, name, grant_admin, access_ok, user_util):
72 user = user_util.create_user()
72 user = user_util.create_user()
73 group = user_util.create_user_group()
73 group = user_util.create_user_group()
74 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
74 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
75 user_util.grant_user_permission_to_user_group(group, user, permission)
75 user_util.grant_user_permission_to_user_group(group, user, permission)
76
76
77 id_, params = build_data(
77 id_, params = build_data(
78 self.apikey_regular,
78 self.apikey_regular,
79 'revoke_user_permission_from_user_group',
79 'revoke_user_permission_from_user_group',
80 usergroupid=group.users_group_name,
80 usergroupid=group.users_group_name,
81 userid=user.username)
81 userid=user.username)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83 if access_ok:
83 if access_ok:
84 expected = {
84 expected = {
85 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
85 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
86 user.username, group.users_group_name
86 user.username, group.users_group_name
87 ),
87 ),
88 'success': True
88 'success': True
89 }
89 }
90 assert_ok(id_, expected, given=response.body)
90 assert_ok(id_, expected, given=response.body)
91 else:
91 else:
92 expected = 'user group `%s` does not exist' % (
92 expected = 'user group `%s` does not exist' % (
93 group.users_group_name)
93 group.users_group_name)
94 assert_error(id_, expected, given=response.body)
94 assert_error(id_, expected, given=response.body)
95
95
96 @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash)
96 @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash)
97 def test_api_revoke_user_permission_from_user_group_exception_when_adding(
97 def test_api_revoke_user_permission_from_user_group_exception_when_adding(
98 self, user_util):
98 self, user_util):
99 user = user_util.create_user()
99 user = user_util.create_user()
100 group = user_util.create_user_group()
100 group = user_util.create_user_group()
101 id_, params = build_data(
101 id_, params = build_data(
102 self.apikey,
102 self.apikey,
103 'revoke_user_permission_from_user_group',
103 'revoke_user_permission_from_user_group',
104 usergroupid=group.users_group_name,
104 usergroupid=group.users_group_name,
105 userid=user.username)
105 userid=user.username)
106 response = api_call(self.app, params)
106 response = api_call(self.app, params)
107
107
108 expected = (
108 expected = (
109 'failed to edit permission for user: `%s` in user group: `%s`' % (
109 'failed to edit permission for user: `%s` in user group: `%s`' % (
110 user.username, group.users_group_name)
110 user.username, group.users_group_name)
111 )
111 )
112 assert_error(id_, expected, given=response.body)
112 assert_error(id_, expected, given=response.body)
@@ -1,213 +1,213 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.vcs.nodes import FileNode
23 from rhodecode.lib.vcs.nodes import FileNode
24 from rhodecode.model.db import User
24 from rhodecode.model.db import User
25 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.model.pull_request import PullRequestModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error)
28 build_data, api_call, assert_ok, assert_error)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdatePullRequest(object):
32 class TestUpdatePullRequest(object):
33
33
34 @pytest.mark.backends("git", "hg")
34 @pytest.mark.backends("git", "hg")
35 def test_api_update_pull_request_title_or_description(
35 def test_api_update_pull_request_title_or_description(
36 self, pr_util, no_notifications):
36 self, pr_util, no_notifications):
37 pull_request = pr_util.create_pull_request()
37 pull_request = pr_util.create_pull_request()
38
38
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'update_pull_request',
40 self.apikey, 'update_pull_request',
41 repoid=pull_request.target_repo.repo_name,
41 repoid=pull_request.target_repo.repo_name,
42 pullrequestid=pull_request.pull_request_id,
42 pullrequestid=pull_request.pull_request_id,
43 title='New TITLE OF A PR',
43 title='New TITLE OF A PR',
44 description='New DESC OF A PR',
44 description='New DESC OF A PR',
45 )
45 )
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47
47
48 expected = {
48 expected = {
49 "msg": "Updated pull request `{}`".format(
49 "msg": "Updated pull request `{}`".format(
50 pull_request.pull_request_id),
50 pull_request.pull_request_id),
51 "pull_request": response.json['result']['pull_request'],
51 "pull_request": response.json['result']['pull_request'],
52 "updated_commits": {"added": [], "common": [], "removed": []},
52 "updated_commits": {"added": [], "common": [], "removed": []},
53 "updated_reviewers": {"added": [], "removed": []},
53 "updated_reviewers": {"added": [], "removed": []},
54 }
54 }
55
55
56 response_json = response.json['result']
56 response_json = response.json['result']
57 assert response_json == expected
57 assert response_json == expected
58 pr = response_json['pull_request']
58 pr = response_json['pull_request']
59 assert pr['title'] == 'New TITLE OF A PR'
59 assert pr['title'] == 'New TITLE OF A PR'
60 assert pr['description'] == 'New DESC OF A PR'
60 assert pr['description'] == 'New DESC OF A PR'
61
61
62 @pytest.mark.backends("git", "hg")
62 @pytest.mark.backends("git", "hg")
63 def test_api_try_update_closed_pull_request(
63 def test_api_try_update_closed_pull_request(
64 self, pr_util, no_notifications):
64 self, pr_util, no_notifications):
65 pull_request = pr_util.create_pull_request()
65 pull_request = pr_util.create_pull_request()
66 PullRequestModel().close_pull_request(
66 PullRequestModel().close_pull_request(
67 pull_request, TEST_USER_ADMIN_LOGIN)
67 pull_request, TEST_USER_ADMIN_LOGIN)
68
68
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'update_pull_request',
70 self.apikey, 'update_pull_request',
71 repoid=pull_request.target_repo.repo_name,
71 repoid=pull_request.target_repo.repo_name,
72 pullrequestid=pull_request.pull_request_id)
72 pullrequestid=pull_request.pull_request_id)
73 response = api_call(self.app, params)
73 response = api_call(self.app, params)
74
74
75 expected = 'pull request `{}` update failed, pull request ' \
75 expected = 'pull request `{}` update failed, pull request ' \
76 'is closed'.format(pull_request.pull_request_id)
76 'is closed'.format(pull_request.pull_request_id)
77
77
78 assert_error(id_, expected, response.body)
78 assert_error(id_, expected, response.body)
79
79
80 @pytest.mark.backends("git", "hg")
80 @pytest.mark.backends("git", "hg")
81 def test_api_update_update_commits(self, pr_util, no_notifications):
81 def test_api_update_update_commits(self, pr_util, no_notifications):
82 commits = [
82 commits = [
83 {'message': 'a'},
83 {'message': 'a'},
84 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
84 {'message': 'b', 'added': [FileNode('file_b', 'test_content\n')]},
85 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
85 {'message': 'c', 'added': [FileNode('file_c', 'test_content\n')]},
86 ]
86 ]
87 pull_request = pr_util.create_pull_request(
87 pull_request = pr_util.create_pull_request(
88 commits=commits, target_head='a', source_head='b', revisions=['b'])
88 commits=commits, target_head='a', source_head='b', revisions=['b'])
89 pr_util.update_source_repository(head='c')
89 pr_util.update_source_repository(head='c')
90 repo = pull_request.source_repo.scm_instance()
90 repo = pull_request.source_repo.scm_instance()
91 commits = [x for x in repo.get_commits()]
91 commits = [x for x in repo.get_commits()]
92 print commits
92 print commits
93
93
94 added_commit_id = commits[-1].raw_id # c commit
94 added_commit_id = commits[-1].raw_id # c commit
95 common_commit_id = commits[1].raw_id # b commit is common ancestor
95 common_commit_id = commits[1].raw_id # b commit is common ancestor
96 total_commits = [added_commit_id, common_commit_id]
96 total_commits = [added_commit_id, common_commit_id]
97
97
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'update_pull_request',
99 self.apikey, 'update_pull_request',
100 repoid=pull_request.target_repo.repo_name,
100 repoid=pull_request.target_repo.repo_name,
101 pullrequestid=pull_request.pull_request_id,
101 pullrequestid=pull_request.pull_request_id,
102 update_commits=True
102 update_commits=True
103 )
103 )
104 response = api_call(self.app, params)
104 response = api_call(self.app, params)
105
105
106 expected = {
106 expected = {
107 "msg": "Updated pull request `{}`".format(
107 "msg": "Updated pull request `{}`".format(
108 pull_request.pull_request_id),
108 pull_request.pull_request_id),
109 "pull_request": response.json['result']['pull_request'],
109 "pull_request": response.json['result']['pull_request'],
110 "updated_commits": {"added": [added_commit_id],
110 "updated_commits": {"added": [added_commit_id],
111 "common": [common_commit_id],
111 "common": [common_commit_id],
112 "total": total_commits,
112 "total": total_commits,
113 "removed": []},
113 "removed": []},
114 "updated_reviewers": {"added": [], "removed": []},
114 "updated_reviewers": {"added": [], "removed": []},
115 }
115 }
116
116
117 assert_ok(id_, expected, response.body)
117 assert_ok(id_, expected, response.body)
118
118
119 @pytest.mark.backends("git", "hg")
119 @pytest.mark.backends("git", "hg")
120 def test_api_update_change_reviewers(
120 def test_api_update_change_reviewers(
121 self, user_util, pr_util, no_notifications):
121 self, user_util, pr_util, no_notifications):
122 a = user_util.create_user()
122 a = user_util.create_user()
123 b = user_util.create_user()
123 b = user_util.create_user()
124 c = user_util.create_user()
124 c = user_util.create_user()
125 new_reviewers = [
125 new_reviewers = [
126 {'username': b.username,'reasons': ['updated via API'],
126 {'username': b.username,'reasons': ['updated via API'],
127 'mandatory':False},
127 'mandatory':False},
128 {'username': c.username, 'reasons': ['updated via API'],
128 {'username': c.username, 'reasons': ['updated via API'],
129 'mandatory':False},
129 'mandatory':False},
130 ]
130 ]
131
131
132 added = [b.username, c.username]
132 added = [b.username, c.username]
133 removed = [a.username]
133 removed = [a.username]
134
134
135 pull_request = pr_util.create_pull_request(
135 pull_request = pr_util.create_pull_request(
136 reviewers=[(a.username, ['added via API'], False, [])])
136 reviewers=[(a.username, ['added via API'], False, [])])
137
137
138 id_, params = build_data(
138 id_, params = build_data(
139 self.apikey, 'update_pull_request',
139 self.apikey, 'update_pull_request',
140 repoid=pull_request.target_repo.repo_name,
140 repoid=pull_request.target_repo.repo_name,
141 pullrequestid=pull_request.pull_request_id,
141 pullrequestid=pull_request.pull_request_id,
142 reviewers=new_reviewers)
142 reviewers=new_reviewers)
143 response = api_call(self.app, params)
143 response = api_call(self.app, params)
144 expected = {
144 expected = {
145 "msg": "Updated pull request `{}`".format(
145 "msg": "Updated pull request `{}`".format(
146 pull_request.pull_request_id),
146 pull_request.pull_request_id),
147 "pull_request": response.json['result']['pull_request'],
147 "pull_request": response.json['result']['pull_request'],
148 "updated_commits": {"added": [], "common": [], "removed": []},
148 "updated_commits": {"added": [], "common": [], "removed": []},
149 "updated_reviewers": {"added": added, "removed": removed},
149 "updated_reviewers": {"added": added, "removed": removed},
150 }
150 }
151
151
152 assert_ok(id_, expected, response.body)
152 assert_ok(id_, expected, response.body)
153
153
154 @pytest.mark.backends("git", "hg")
154 @pytest.mark.backends("git", "hg")
155 def test_api_update_bad_user_in_reviewers(self, pr_util):
155 def test_api_update_bad_user_in_reviewers(self, pr_util):
156 pull_request = pr_util.create_pull_request()
156 pull_request = pr_util.create_pull_request()
157
157
158 id_, params = build_data(
158 id_, params = build_data(
159 self.apikey, 'update_pull_request',
159 self.apikey, 'update_pull_request',
160 repoid=pull_request.target_repo.repo_name,
160 repoid=pull_request.target_repo.repo_name,
161 pullrequestid=pull_request.pull_request_id,
161 pullrequestid=pull_request.pull_request_id,
162 reviewers=[{'username': 'bad_name'}])
162 reviewers=[{'username': 'bad_name'}])
163 response = api_call(self.app, params)
163 response = api_call(self.app, params)
164
164
165 expected = 'user `bad_name` does not exist'
165 expected = 'user `bad_name` does not exist'
166
166
167 assert_error(id_, expected, response.body)
167 assert_error(id_, expected, response.body)
168
168
169 @pytest.mark.backends("git", "hg")
169 @pytest.mark.backends("git", "hg")
170 def test_api_update_repo_error(self, pr_util):
170 def test_api_update_repo_error(self, pr_util):
171 pull_request = pr_util.create_pull_request()
171 pull_request = pr_util.create_pull_request()
172 id_, params = build_data(
172 id_, params = build_data(
173 self.apikey, 'update_pull_request',
173 self.apikey, 'update_pull_request',
174 repoid='fake',
174 repoid='fake',
175 pullrequestid=pull_request.pull_request_id,
175 pullrequestid=pull_request.pull_request_id,
176 reviewers=[{'username': 'bad_name'}])
176 reviewers=[{'username': 'bad_name'}])
177 response = api_call(self.app, params)
177 response = api_call(self.app, params)
178
178
179 expected = 'repository `fake` does not exist'
179 expected = 'repository `fake` does not exist'
180
180
181 response_json = response.json['error']
181 response_json = response.json['error']
182 assert response_json == expected
182 assert response_json == expected
183
183
184 @pytest.mark.backends("git", "hg")
184 @pytest.mark.backends("git", "hg")
185 def test_api_update_pull_request_error(self, pr_util):
185 def test_api_update_pull_request_error(self, pr_util):
186 pull_request = pr_util.create_pull_request()
186 pull_request = pr_util.create_pull_request()
187
187
188 id_, params = build_data(
188 id_, params = build_data(
189 self.apikey, 'update_pull_request',
189 self.apikey, 'update_pull_request',
190 repoid=pull_request.target_repo.repo_name,
190 repoid=pull_request.target_repo.repo_name,
191 pullrequestid=999999,
191 pullrequestid=999999,
192 reviewers=[{'username': 'bad_name'}])
192 reviewers=[{'username': 'bad_name'}])
193 response = api_call(self.app, params)
193 response = api_call(self.app, params)
194
194
195 expected = 'pull request `999999` does not exist'
195 expected = 'pull request `999999` does not exist'
196 assert_error(id_, expected, response.body)
196 assert_error(id_, expected, response.body)
197
197
198 @pytest.mark.backends("git", "hg")
198 @pytest.mark.backends("git", "hg")
199 def test_api_update_pull_request_no_perms_to_update(
199 def test_api_update_pull_request_no_perms_to_update(
200 self, user_util, pr_util):
200 self, user_util, pr_util):
201 user = user_util.create_user()
201 user = user_util.create_user()
202 pull_request = pr_util.create_pull_request()
202 pull_request = pr_util.create_pull_request()
203
203
204 id_, params = build_data(
204 id_, params = build_data(
205 user.api_key, 'update_pull_request',
205 user.api_key, 'update_pull_request',
206 repoid=pull_request.target_repo.repo_name,
206 repoid=pull_request.target_repo.repo_name,
207 pullrequestid=pull_request.pull_request_id,)
207 pullrequestid=pull_request.pull_request_id,)
208 response = api_call(self.app, params)
208 response = api_call(self.app, params)
209
209
210 expected = ('pull request `%s` update failed, '
210 expected = ('pull request `%s` update failed, '
211 'no permission to update.') % pull_request.pull_request_id
211 'no permission to update.') % pull_request.pull_request_id
212
212
213 assert_error(id_, expected, response.body)
213 assert_error(id_, expected, response.body)
@@ -1,192 +1,192 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.plugin import http_host_stub, http_host_only_stub
29 from rhodecode.tests.plugin import http_host_stub, http_host_only_stub
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33 UPDATE_REPO_NAME = 'api_update_me'
33 UPDATE_REPO_NAME = 'api_update_me'
34
34
35
35
36 class SAME_AS_UPDATES(object):
36 class SAME_AS_UPDATES(object):
37 """ Constant used for tests below """
37 """ Constant used for tests below """
38
38
39
39
40 @pytest.mark.usefixtures("testuser_api", "app")
40 @pytest.mark.usefixtures("testuser_api", "app")
41 class TestApiUpdateRepo(object):
41 class TestApiUpdateRepo(object):
42
42
43 @pytest.mark.parametrize("updates, expected", [
43 @pytest.mark.parametrize("updates, expected", [
44 ({'owner': TEST_USER_REGULAR_LOGIN},
44 ({'owner': TEST_USER_REGULAR_LOGIN},
45 SAME_AS_UPDATES),
45 SAME_AS_UPDATES),
46
46
47 ({'description': 'new description'},
47 ({'description': 'new description'},
48 SAME_AS_UPDATES),
48 SAME_AS_UPDATES),
49
49
50 ({'clone_uri': 'http://foo.com/repo'},
50 ({'clone_uri': 'http://foo.com/repo'},
51 SAME_AS_UPDATES),
51 SAME_AS_UPDATES),
52
52
53 ({'clone_uri': None},
53 ({'clone_uri': None},
54 {'clone_uri': ''}),
54 {'clone_uri': ''}),
55
55
56 ({'clone_uri': ''},
56 ({'clone_uri': ''},
57 {'clone_uri': ''}),
57 {'clone_uri': ''}),
58
58
59 ({'landing_rev': 'rev:tip'},
59 ({'landing_rev': 'rev:tip'},
60 {'landing_rev': ['rev', 'tip']}),
60 {'landing_rev': ['rev', 'tip']}),
61
61
62 ({'enable_statistics': True},
62 ({'enable_statistics': True},
63 SAME_AS_UPDATES),
63 SAME_AS_UPDATES),
64
64
65 ({'enable_locking': True},
65 ({'enable_locking': True},
66 SAME_AS_UPDATES),
66 SAME_AS_UPDATES),
67
67
68 ({'enable_downloads': True},
68 ({'enable_downloads': True},
69 SAME_AS_UPDATES),
69 SAME_AS_UPDATES),
70
70
71 ({'repo_name': 'new_repo_name'},
71 ({'repo_name': 'new_repo_name'},
72 {
72 {
73 'repo_name': 'new_repo_name',
73 'repo_name': 'new_repo_name',
74 'url': 'http://{}/new_repo_name'.format(http_host_only_stub())
74 'url': 'http://{}/new_repo_name'.format(http_host_only_stub())
75 }),
75 }),
76
76
77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
77 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
78 '_group': 'test_group_for_update'},
78 '_group': 'test_group_for_update'},
79 {
79 {
80 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
80 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
81 'url': 'http://{}/test_group_for_update/{}'.format(
81 'url': 'http://{}/test_group_for_update/{}'.format(
82 http_host_only_stub(), UPDATE_REPO_NAME)
82 http_host_only_stub(), UPDATE_REPO_NAME)
83 }),
83 }),
84 ])
84 ])
85 def test_api_update_repo(self, updates, expected, backend):
85 def test_api_update_repo(self, updates, expected, backend):
86 repo_name = UPDATE_REPO_NAME
86 repo_name = UPDATE_REPO_NAME
87 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
87 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
88 if updates.get('_group'):
88 if updates.get('_group'):
89 fixture.create_repo_group(updates['_group'])
89 fixture.create_repo_group(updates['_group'])
90
90
91 expected_api_data = repo.get_api_data(include_secrets=True)
91 expected_api_data = repo.get_api_data(include_secrets=True)
92 if expected is SAME_AS_UPDATES:
92 if expected is SAME_AS_UPDATES:
93 expected_api_data.update(updates)
93 expected_api_data.update(updates)
94 else:
94 else:
95 expected_api_data.update(expected)
95 expected_api_data.update(expected)
96
96
97 id_, params = build_data(
97 id_, params = build_data(
98 self.apikey, 'update_repo', repoid=repo_name, **updates)
98 self.apikey, 'update_repo', repoid=repo_name, **updates)
99 response = api_call(self.app, params)
99 response = api_call(self.app, params)
100
100
101 if updates.get('repo_name'):
101 if updates.get('repo_name'):
102 repo_name = updates['repo_name']
102 repo_name = updates['repo_name']
103
103
104 try:
104 try:
105 expected = {
105 expected = {
106 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
106 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
107 'repository': jsonify(expected_api_data)
107 'repository': jsonify(expected_api_data)
108 }
108 }
109 assert_ok(id_, expected, given=response.body)
109 assert_ok(id_, expected, given=response.body)
110 finally:
110 finally:
111 fixture.destroy_repo(repo_name)
111 fixture.destroy_repo(repo_name)
112 if updates.get('_group'):
112 if updates.get('_group'):
113 fixture.destroy_repo_group(updates['_group'])
113 fixture.destroy_repo_group(updates['_group'])
114
114
115 def test_api_update_repo_fork_of_field(self, backend):
115 def test_api_update_repo_fork_of_field(self, backend):
116 master_repo = backend.create_repo()
116 master_repo = backend.create_repo()
117 repo = backend.create_repo()
117 repo = backend.create_repo()
118 updates = {
118 updates = {
119 'fork_of': master_repo.repo_name,
119 'fork_of': master_repo.repo_name,
120 'fork_of_id': master_repo.repo_id
120 'fork_of_id': master_repo.repo_id
121 }
121 }
122 expected_api_data = repo.get_api_data(include_secrets=True)
122 expected_api_data = repo.get_api_data(include_secrets=True)
123 expected_api_data.update(updates)
123 expected_api_data.update(updates)
124
124
125 id_, params = build_data(
125 id_, params = build_data(
126 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
126 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
127 response = api_call(self.app, params)
127 response = api_call(self.app, params)
128 expected = {
128 expected = {
129 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
129 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
130 'repository': jsonify(expected_api_data)
130 'repository': jsonify(expected_api_data)
131 }
131 }
132 assert_ok(id_, expected, given=response.body)
132 assert_ok(id_, expected, given=response.body)
133 result = response.json['result']['repository']
133 result = response.json['result']['repository']
134 assert result['fork_of'] == master_repo.repo_name
134 assert result['fork_of'] == master_repo.repo_name
135 assert result['fork_of_id'] == master_repo.repo_id
135 assert result['fork_of_id'] == master_repo.repo_id
136
136
137 def test_api_update_repo_fork_of_not_found(self, backend):
137 def test_api_update_repo_fork_of_not_found(self, backend):
138 master_repo_name = 'fake-parent-repo'
138 master_repo_name = 'fake-parent-repo'
139 repo = backend.create_repo()
139 repo = backend.create_repo()
140 updates = {
140 updates = {
141 'fork_of': master_repo_name
141 'fork_of': master_repo_name
142 }
142 }
143 id_, params = build_data(
143 id_, params = build_data(
144 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
144 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
145 response = api_call(self.app, params)
145 response = api_call(self.app, params)
146 expected = {
146 expected = {
147 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
147 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
148 master_repo_name)}
148 master_repo_name)}
149 assert_error(id_, expected, given=response.body)
149 assert_error(id_, expected, given=response.body)
150
150
151 def test_api_update_repo_with_repo_group_not_existing(self):
151 def test_api_update_repo_with_repo_group_not_existing(self):
152 repo_name = 'admin_owned'
152 repo_name = 'admin_owned'
153 fake_repo_group = 'test_group_for_update'
153 fake_repo_group = 'test_group_for_update'
154 fixture.create_repo(repo_name)
154 fixture.create_repo(repo_name)
155 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
155 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
156 id_, params = build_data(
156 id_, params = build_data(
157 self.apikey, 'update_repo', repoid=repo_name, **updates)
157 self.apikey, 'update_repo', repoid=repo_name, **updates)
158 response = api_call(self.app, params)
158 response = api_call(self.app, params)
159 try:
159 try:
160 expected = {
160 expected = {
161 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
161 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
162 }
162 }
163 assert_error(id_, expected, given=response.body)
163 assert_error(id_, expected, given=response.body)
164 finally:
164 finally:
165 fixture.destroy_repo(repo_name)
165 fixture.destroy_repo(repo_name)
166
166
167 def test_api_update_repo_regular_user_not_allowed(self):
167 def test_api_update_repo_regular_user_not_allowed(self):
168 repo_name = 'admin_owned'
168 repo_name = 'admin_owned'
169 fixture.create_repo(repo_name)
169 fixture.create_repo(repo_name)
170 updates = {'active': False}
170 updates = {'active': False}
171 id_, params = build_data(
171 id_, params = build_data(
172 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
172 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
173 response = api_call(self.app, params)
173 response = api_call(self.app, params)
174 try:
174 try:
175 expected = 'repository `%s` does not exist' % (repo_name,)
175 expected = 'repository `%s` does not exist' % (repo_name,)
176 assert_error(id_, expected, given=response.body)
176 assert_error(id_, expected, given=response.body)
177 finally:
177 finally:
178 fixture.destroy_repo(repo_name)
178 fixture.destroy_repo(repo_name)
179
179
180 @mock.patch.object(RepoModel, 'update', crash)
180 @mock.patch.object(RepoModel, 'update', crash)
181 def test_api_update_repo_exception_occurred(self, backend):
181 def test_api_update_repo_exception_occurred(self, backend):
182 repo_name = UPDATE_REPO_NAME
182 repo_name = UPDATE_REPO_NAME
183 fixture.create_repo(repo_name, repo_type=backend.alias)
183 fixture.create_repo(repo_name, repo_type=backend.alias)
184 id_, params = build_data(
184 id_, params = build_data(
185 self.apikey, 'update_repo', repoid=repo_name,
185 self.apikey, 'update_repo', repoid=repo_name,
186 owner=TEST_USER_ADMIN_LOGIN,)
186 owner=TEST_USER_ADMIN_LOGIN,)
187 response = api_call(self.app, params)
187 response = api_call(self.app, params)
188 try:
188 try:
189 expected = 'failed to update repo `%s`' % (repo_name,)
189 expected = 'failed to update repo `%s`' % (repo_name,)
190 assert_error(id_, expected, given=response.body)
190 assert_error(id_, expected, given=response.body)
191 finally:
191 finally:
192 fixture.destroy_repo(repo_name)
192 fixture.destroy_repo(repo_name)
@@ -1,150 +1,150 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22
22
23 import pytest
23 import pytest
24
24
25 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok)
28 build_data, api_call, assert_error, assert_ok)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestApiUpdateRepoGroup(object):
32 class TestApiUpdateRepoGroup(object):
33
33
34 def test_update_group_name(self, user_util):
34 def test_update_group_name(self, user_util):
35 new_group_name = 'new-group'
35 new_group_name = 'new-group'
36 initial_name = self._update(user_util, group_name=new_group_name)
36 initial_name = self._update(user_util, group_name=new_group_name)
37 assert RepoGroupModel()._get_repo_group(initial_name) is None
37 assert RepoGroupModel()._get_repo_group(initial_name) is None
38 new_group = RepoGroupModel()._get_repo_group(new_group_name)
38 new_group = RepoGroupModel()._get_repo_group(new_group_name)
39 assert new_group is not None
39 assert new_group is not None
40 assert new_group.full_path == new_group_name
40 assert new_group.full_path == new_group_name
41
41
42 def test_update_group_name_change_parent(self, user_util):
42 def test_update_group_name_change_parent(self, user_util):
43
43
44 parent_group = user_util.create_repo_group()
44 parent_group = user_util.create_repo_group()
45 parent_group_name = parent_group.name
45 parent_group_name = parent_group.name
46
46
47 expected_group_name = '{}/{}'.format(parent_group_name, 'new-group')
47 expected_group_name = '{}/{}'.format(parent_group_name, 'new-group')
48 initial_name = self._update(user_util, group_name=expected_group_name)
48 initial_name = self._update(user_util, group_name=expected_group_name)
49
49
50 repo_group = RepoGroupModel()._get_repo_group(expected_group_name)
50 repo_group = RepoGroupModel()._get_repo_group(expected_group_name)
51
51
52 assert repo_group is not None
52 assert repo_group is not None
53 assert repo_group.group_name == expected_group_name
53 assert repo_group.group_name == expected_group_name
54 assert repo_group.full_path == expected_group_name
54 assert repo_group.full_path == expected_group_name
55 assert RepoGroupModel()._get_repo_group(initial_name) is None
55 assert RepoGroupModel()._get_repo_group(initial_name) is None
56
56
57 new_path = os.path.join(
57 new_path = os.path.join(
58 RepoGroupModel().repos_path, *repo_group.full_path_splitted)
58 RepoGroupModel().repos_path, *repo_group.full_path_splitted)
59 assert os.path.exists(new_path)
59 assert os.path.exists(new_path)
60
60
61 def test_update_enable_locking(self, user_util):
61 def test_update_enable_locking(self, user_util):
62 initial_name = self._update(user_util, enable_locking=True)
62 initial_name = self._update(user_util, enable_locking=True)
63 repo_group = RepoGroupModel()._get_repo_group(initial_name)
63 repo_group = RepoGroupModel()._get_repo_group(initial_name)
64 assert repo_group.enable_locking is True
64 assert repo_group.enable_locking is True
65
65
66 def test_update_description(self, user_util):
66 def test_update_description(self, user_util):
67 description = 'New description'
67 description = 'New description'
68 initial_name = self._update(user_util, description=description)
68 initial_name = self._update(user_util, description=description)
69 repo_group = RepoGroupModel()._get_repo_group(initial_name)
69 repo_group = RepoGroupModel()._get_repo_group(initial_name)
70 assert repo_group.group_description == description
70 assert repo_group.group_description == description
71
71
72 def test_update_owner(self, user_util):
72 def test_update_owner(self, user_util):
73 owner = self.TEST_USER_LOGIN
73 owner = self.TEST_USER_LOGIN
74 initial_name = self._update(user_util, owner=owner)
74 initial_name = self._update(user_util, owner=owner)
75 repo_group = RepoGroupModel()._get_repo_group(initial_name)
75 repo_group = RepoGroupModel()._get_repo_group(initial_name)
76 assert repo_group.user.username == owner
76 assert repo_group.user.username == owner
77
77
78 def test_update_group_name_conflict_with_existing(self, user_util):
78 def test_update_group_name_conflict_with_existing(self, user_util):
79 group_1 = user_util.create_repo_group()
79 group_1 = user_util.create_repo_group()
80 group_2 = user_util.create_repo_group()
80 group_2 = user_util.create_repo_group()
81 repo_group_name_1 = group_1.group_name
81 repo_group_name_1 = group_1.group_name
82 repo_group_name_2 = group_2.group_name
82 repo_group_name_2 = group_2.group_name
83
83
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey, 'update_repo_group', repogroupid=repo_group_name_1,
85 self.apikey, 'update_repo_group', repogroupid=repo_group_name_1,
86 group_name=repo_group_name_2)
86 group_name=repo_group_name_2)
87 response = api_call(self.app, params)
87 response = api_call(self.app, params)
88 expected = {
88 expected = {
89 'unique_repo_group_name':
89 'unique_repo_group_name':
90 'Repository group with name `{}` already exists'.format(
90 'Repository group with name `{}` already exists'.format(
91 repo_group_name_2)}
91 repo_group_name_2)}
92 assert_error(id_, expected, given=response.body)
92 assert_error(id_, expected, given=response.body)
93
93
94 def test_api_update_repo_group_by_regular_user_no_permission(self, user_util):
94 def test_api_update_repo_group_by_regular_user_no_permission(self, user_util):
95 temp_user = user_util.create_user()
95 temp_user = user_util.create_user()
96 temp_user_api_key = temp_user.api_key
96 temp_user_api_key = temp_user.api_key
97 parent_group = user_util.create_repo_group()
97 parent_group = user_util.create_repo_group()
98 repo_group_name = parent_group.group_name
98 repo_group_name = parent_group.group_name
99 id_, params = build_data(
99 id_, params = build_data(
100 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name)
100 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name)
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102 expected = 'repository group `%s` does not exist' % (repo_group_name,)
102 expected = 'repository group `%s` does not exist' % (repo_group_name,)
103 assert_error(id_, expected, given=response.body)
103 assert_error(id_, expected, given=response.body)
104
104
105 def test_api_update_repo_group_regular_user_no_root_write_permissions(
105 def test_api_update_repo_group_regular_user_no_root_write_permissions(
106 self, user_util):
106 self, user_util):
107 temp_user = user_util.create_user()
107 temp_user = user_util.create_user()
108 temp_user_api_key = temp_user.api_key
108 temp_user_api_key = temp_user.api_key
109 parent_group = user_util.create_repo_group(owner=temp_user.username)
109 parent_group = user_util.create_repo_group(owner=temp_user.username)
110 repo_group_name = parent_group.group_name
110 repo_group_name = parent_group.group_name
111
111
112 id_, params = build_data(
112 id_, params = build_data(
113 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name,
113 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name,
114 group_name='at-root-level')
114 group_name='at-root-level')
115 response = api_call(self.app, params)
115 response = api_call(self.app, params)
116 expected = {
116 expected = {
117 'repo_group': 'You do not have the permission to store '
117 'repo_group': 'You do not have the permission to store '
118 'repository groups in the root location.'}
118 'repository groups in the root location.'}
119 assert_error(id_, expected, given=response.body)
119 assert_error(id_, expected, given=response.body)
120
120
121 def _update(self, user_util, **kwargs):
121 def _update(self, user_util, **kwargs):
122 repo_group = user_util.create_repo_group()
122 repo_group = user_util.create_repo_group()
123 initial_name = repo_group.name
123 initial_name = repo_group.name
124 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
124 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
125 user_util.grant_user_permission_to_repo_group(
125 user_util.grant_user_permission_to_repo_group(
126 repo_group, user, 'group.admin')
126 repo_group, user, 'group.admin')
127
127
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey, 'update_repo_group', repogroupid=initial_name,
129 self.apikey, 'update_repo_group', repogroupid=initial_name,
130 **kwargs)
130 **kwargs)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132
132
133 repo_group = RepoGroupModel.cls.get(repo_group.group_id)
133 repo_group = RepoGroupModel.cls.get(repo_group.group_id)
134
134
135 expected = {
135 expected = {
136 'msg': 'updated repository group ID:{} {}'.format(
136 'msg': 'updated repository group ID:{} {}'.format(
137 repo_group.group_id, repo_group.group_name),
137 repo_group.group_id, repo_group.group_name),
138 'repo_group': {
138 'repo_group': {
139 'repositories': [],
139 'repositories': [],
140 'group_name': repo_group.group_name,
140 'group_name': repo_group.group_name,
141 'group_description': repo_group.group_description,
141 'group_description': repo_group.group_description,
142 'owner': repo_group.user.username,
142 'owner': repo_group.user.username,
143 'group_id': repo_group.group_id,
143 'group_id': repo_group.group_id,
144 'parent_group': (
144 'parent_group': (
145 repo_group.parent_group.name
145 repo_group.parent_group.name
146 if repo_group.parent_group else None)
146 if repo_group.parent_group else None)
147 }
147 }
148 }
148 }
149 assert_ok(id_, expected, given=response.body)
149 assert_ok(id_, expected, given=response.body)
150 return initial_name
150 return initial_name
@@ -1,116 +1,116 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import User
24 from rhodecode.model.db import User
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error, crash, jsonify)
28 build_data, api_call, assert_ok, assert_error, crash, jsonify)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdateUser(object):
32 class TestUpdateUser(object):
33 @pytest.mark.parametrize("name, expected", [
33 @pytest.mark.parametrize("name, expected", [
34 ('firstname', 'new_username'),
34 ('firstname', 'new_username'),
35 ('lastname', 'new_username'),
35 ('lastname', 'new_username'),
36 ('email', 'new_username'),
36 ('email', 'new_username'),
37 ('admin', True),
37 ('admin', True),
38 ('admin', False),
38 ('admin', False),
39 ('extern_type', 'ldap'),
39 ('extern_type', 'ldap'),
40 ('extern_type', None),
40 ('extern_type', None),
41 ('extern_name', 'test'),
41 ('extern_name', 'test'),
42 ('extern_name', None),
42 ('extern_name', None),
43 ('active', False),
43 ('active', False),
44 ('active', True),
44 ('active', True),
45 ('password', 'newpass')
45 ('password', 'newpass')
46 ])
46 ])
47 def test_api_update_user(self, name, expected, user_util):
47 def test_api_update_user(self, name, expected, user_util):
48 usr = user_util.create_user()
48 usr = user_util.create_user()
49
49
50 kw = {name: expected, 'userid': usr.user_id}
50 kw = {name: expected, 'userid': usr.user_id}
51 id_, params = build_data(self.apikey, 'update_user', **kw)
51 id_, params = build_data(self.apikey, 'update_user', **kw)
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 ret = {
54 ret = {
55 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
55 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
56 'user': jsonify(
56 'user': jsonify(
57 UserModel()
57 UserModel()
58 .get_by_username(usr.username)
58 .get_by_username(usr.username)
59 .get_api_data(include_secrets=True)
59 .get_api_data(include_secrets=True)
60 )
60 )
61 }
61 }
62
62
63 expected = ret
63 expected = ret
64 assert_ok(id_, expected, given=response.body)
64 assert_ok(id_, expected, given=response.body)
65
65
66 def test_api_update_user_no_changed_params(self):
66 def test_api_update_user_no_changed_params(self):
67 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
67 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
68 ret = jsonify(usr.get_api_data(include_secrets=True))
68 ret = jsonify(usr.get_api_data(include_secrets=True))
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
70 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
71
71
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73 ret = {
73 ret = {
74 'msg': 'updated user ID:%s %s' % (
74 'msg': 'updated user ID:%s %s' % (
75 usr.user_id, TEST_USER_ADMIN_LOGIN),
75 usr.user_id, TEST_USER_ADMIN_LOGIN),
76 'user': ret
76 'user': ret
77 }
77 }
78 expected = ret
78 expected = ret
79 assert_ok(id_, expected, given=response.body)
79 assert_ok(id_, expected, given=response.body)
80
80
81 def test_api_update_user_by_user_id(self):
81 def test_api_update_user_by_user_id(self):
82 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
82 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
83 ret = jsonify(usr.get_api_data(include_secrets=True))
83 ret = jsonify(usr.get_api_data(include_secrets=True))
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey, 'update_user', userid=usr.user_id)
85 self.apikey, 'update_user', userid=usr.user_id)
86
86
87 response = api_call(self.app, params)
87 response = api_call(self.app, params)
88 ret = {
88 ret = {
89 'msg': 'updated user ID:%s %s' % (
89 'msg': 'updated user ID:%s %s' % (
90 usr.user_id, TEST_USER_ADMIN_LOGIN),
90 usr.user_id, TEST_USER_ADMIN_LOGIN),
91 'user': ret
91 'user': ret
92 }
92 }
93 expected = ret
93 expected = ret
94 assert_ok(id_, expected, given=response.body)
94 assert_ok(id_, expected, given=response.body)
95
95
96 def test_api_update_user_default_user(self):
96 def test_api_update_user_default_user(self):
97 usr = User.get_default_user()
97 usr = User.get_default_user()
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'update_user', userid=usr.user_id)
99 self.apikey, 'update_user', userid=usr.user_id)
100
100
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102 expected = 'editing default user is forbidden'
102 expected = 'editing default user is forbidden'
103 assert_error(id_, expected, given=response.body)
103 assert_error(id_, expected, given=response.body)
104
104
105 @mock.patch.object(UserModel, 'update_user', crash)
105 @mock.patch.object(UserModel, 'update_user', crash)
106 def test_api_update_user_when_exception_happens(self):
106 def test_api_update_user_when_exception_happens(self):
107 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
107 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
108 ret = jsonify(usr.get_api_data(include_secrets=True))
108 ret = jsonify(usr.get_api_data(include_secrets=True))
109 id_, params = build_data(
109 id_, params = build_data(
110 self.apikey, 'update_user', userid=usr.user_id)
110 self.apikey, 'update_user', userid=usr.user_id)
111
111
112 response = api_call(self.app, params)
112 response = api_call(self.app, params)
113 ret = 'failed to update user `%s`' % (usr.user_id,)
113 ret = 'failed to update user `%s`' % (usr.user_id,)
114
114
115 expected = ret
115 expected = ret
116 assert_error(id_, expected, given=response.body)
116 assert_error(id_, expected, given=response.body)
@@ -1,110 +1,110 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
26 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestUpdateUserGroup(object):
32 class TestUpdateUserGroup(object):
33 @pytest.mark.parametrize("changing_attr, updates", [
33 @pytest.mark.parametrize("changing_attr, updates", [
34 ('group_name', {'group_name': 'new_group_name'}),
34 ('group_name', {'group_name': 'new_group_name'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
35 ('group_name', {'group_name': 'test_group_for_update'}),
36 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
36 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
37 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
37 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
38 ('active', {'active': False}),
38 ('active', {'active': False}),
39 ('active', {'active': True})
39 ('active', {'active': True})
40 ])
40 ])
41 def test_api_update_user_group(self, changing_attr, updates, user_util):
41 def test_api_update_user_group(self, changing_attr, updates, user_util):
42 user_group = user_util.create_user_group()
42 user_group = user_util.create_user_group()
43 group_name = user_group.users_group_name
43 group_name = user_group.users_group_name
44 expected_api_data = user_group.get_api_data()
44 expected_api_data = user_group.get_api_data()
45 expected_api_data.update(updates)
45 expected_api_data.update(updates)
46
46
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey, 'update_user_group', usergroupid=group_name,
48 self.apikey, 'update_user_group', usergroupid=group_name,
49 **updates)
49 **updates)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 expected = {
52 expected = {
53 'msg': 'updated user group ID:%s %s' % (
53 'msg': 'updated user group ID:%s %s' % (
54 user_group.users_group_id, user_group.users_group_name),
54 user_group.users_group_id, user_group.users_group_name),
55 'user_group': jsonify(expected_api_data)
55 'user_group': jsonify(expected_api_data)
56 }
56 }
57 assert_ok(id_, expected, given=response.body)
57 assert_ok(id_, expected, given=response.body)
58
58
59 @pytest.mark.parametrize("changing_attr, updates", [
59 @pytest.mark.parametrize("changing_attr, updates", [
60 # TODO: mikhail: decide if we need to test against the commented params
60 # TODO: mikhail: decide if we need to test against the commented params
61 # ('group_name', {'group_name': 'new_group_name'}),
61 # ('group_name', {'group_name': 'new_group_name'}),
62 # ('group_name', {'group_name': 'test_group_for_update'}),
62 # ('group_name', {'group_name': 'test_group_for_update'}),
63 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
63 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
64 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
64 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
65 ('active', {'active': False}),
65 ('active', {'active': False}),
66 ('active', {'active': True})
66 ('active', {'active': True})
67 ])
67 ])
68 def test_api_update_user_group_regular_user(
68 def test_api_update_user_group_regular_user(
69 self, changing_attr, updates, user_util):
69 self, changing_attr, updates, user_util):
70 user_group = user_util.create_user_group()
70 user_group = user_util.create_user_group()
71 group_name = user_group.users_group_name
71 group_name = user_group.users_group_name
72 expected_api_data = user_group.get_api_data()
72 expected_api_data = user_group.get_api_data()
73 expected_api_data.update(updates)
73 expected_api_data.update(updates)
74
74
75
75
76 # grant permission to this user
76 # grant permission to this user
77 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
77 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
78
78
79 user_util.grant_user_permission_to_user_group(
79 user_util.grant_user_permission_to_user_group(
80 user_group, user, 'usergroup.admin')
80 user_group, user, 'usergroup.admin')
81 id_, params = build_data(
81 id_, params = build_data(
82 self.apikey_regular, 'update_user_group',
82 self.apikey_regular, 'update_user_group',
83 usergroupid=group_name, **updates)
83 usergroupid=group_name, **updates)
84 response = api_call(self.app, params)
84 response = api_call(self.app, params)
85 expected = {
85 expected = {
86 'msg': 'updated user group ID:%s %s' % (
86 'msg': 'updated user group ID:%s %s' % (
87 user_group.users_group_id, user_group.users_group_name),
87 user_group.users_group_id, user_group.users_group_name),
88 'user_group': jsonify(expected_api_data)
88 'user_group': jsonify(expected_api_data)
89 }
89 }
90 assert_ok(id_, expected, given=response.body)
90 assert_ok(id_, expected, given=response.body)
91
91
92 def test_api_update_user_group_regular_user_no_permission(self, user_util):
92 def test_api_update_user_group_regular_user_no_permission(self, user_util):
93 user_group = user_util.create_user_group()
93 user_group = user_util.create_user_group()
94 group_name = user_group.users_group_name
94 group_name = user_group.users_group_name
95 id_, params = build_data(
95 id_, params = build_data(
96 self.apikey_regular, 'update_user_group', usergroupid=group_name)
96 self.apikey_regular, 'update_user_group', usergroupid=group_name)
97 response = api_call(self.app, params)
97 response = api_call(self.app, params)
98
98
99 expected = 'user group `%s` does not exist' % (group_name)
99 expected = 'user group `%s` does not exist' % (group_name)
100 assert_error(id_, expected, given=response.body)
100 assert_error(id_, expected, given=response.body)
101
101
102 @mock.patch.object(UserGroupModel, 'update', crash)
102 @mock.patch.object(UserGroupModel, 'update', crash)
103 def test_api_update_user_group_exception_occurred(self, user_util):
103 def test_api_update_user_group_exception_occurred(self, user_util):
104 user_group = user_util.create_user_group()
104 user_group = user_util.create_user_group()
105 group_name = user_group.users_group_name
105 group_name = user_group.users_group_name
106 id_, params = build_data(
106 id_, params = build_data(
107 self.apikey, 'update_user_group', usergroupid=group_name)
107 self.apikey, 'update_user_group', usergroupid=group_name)
108 response = api_call(self.app, params)
108 response = api_call(self.app, params)
109 expected = 'failed to update user group `%s`' % (group_name,)
109 expected = 'failed to update user group `%s`' % (group_name,)
110 assert_error(id_, expected, given=response.body)
110 assert_error(id_, expected, given=response.body)
@@ -1,294 +1,294 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23 from mock import Mock, patch
23 from mock import Mock, patch
24
24
25 from rhodecode.api import utils
25 from rhodecode.api import utils
26 from rhodecode.api import JSONRPCError
26 from rhodecode.api import JSONRPCError
27 from rhodecode.lib.vcs.exceptions import RepositoryError
27 from rhodecode.lib.vcs.exceptions import RepositoryError
28
28
29
29
30 class TestGetCommitOrError(object):
30 class TestGetCommitOrError(object):
31 def setup(self):
31 def setup(self):
32 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
32 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
33
33
34 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d', 'branch:name'])
34 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d', 'branch:name'])
35 def test_ref_cannot_be_parsed(self, ref):
35 def test_ref_cannot_be_parsed(self, ref):
36 repo = Mock()
36 repo = Mock()
37 with pytest.raises(JSONRPCError) as excinfo:
37 with pytest.raises(JSONRPCError) as excinfo:
38 utils.get_commit_or_error(ref, repo)
38 utils.get_commit_or_error(ref, repo)
39 expected_message = (
39 expected_message = (
40 'Ref `{ref}` given in a wrong format. Please check the API'
40 'Ref `{ref}` given in a wrong format. Please check the API'
41 ' documentation for more details'.format(ref=ref)
41 ' documentation for more details'.format(ref=ref)
42 )
42 )
43 assert excinfo.value.message == expected_message
43 assert excinfo.value.message == expected_message
44
44
45 def test_success_with_hash_specified(self):
45 def test_success_with_hash_specified(self):
46 repo = Mock()
46 repo = Mock()
47 ref_type = 'branch'
47 ref_type = 'branch'
48 ref = '{}:master:{}'.format(ref_type, self.commit_hash)
48 ref = '{}:master:{}'.format(ref_type, self.commit_hash)
49
49
50 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
50 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
51 result = utils.get_commit_or_error(ref, repo)
51 result = utils.get_commit_or_error(ref, repo)
52 get_commit.assert_called_once_with(
52 get_commit.assert_called_once_with(
53 repo, self.commit_hash)
53 repo, self.commit_hash)
54 assert result == get_commit()
54 assert result == get_commit()
55
55
56 def test_raises_an_error_when_commit_not_found(self):
56 def test_raises_an_error_when_commit_not_found(self):
57 repo = Mock()
57 repo = Mock()
58 ref = 'branch:master:{}'.format(self.commit_hash)
58 ref = 'branch:master:{}'.format(self.commit_hash)
59
59
60 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
60 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
61 get_commit.side_effect = RepositoryError('Commit not found')
61 get_commit.side_effect = RepositoryError('Commit not found')
62 with pytest.raises(JSONRPCError) as excinfo:
62 with pytest.raises(JSONRPCError) as excinfo:
63 utils.get_commit_or_error(ref, repo)
63 utils.get_commit_or_error(ref, repo)
64 expected_message = 'Ref `{}` does not exist'.format(ref)
64 expected_message = 'Ref `{}` does not exist'.format(ref)
65 assert excinfo.value.message == expected_message
65 assert excinfo.value.message == expected_message
66
66
67
67
68 class TestResolveRefOrError(object):
68 class TestResolveRefOrError(object):
69 def setup(self):
69 def setup(self):
70 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
70 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
71
71
72 def test_success_with_no_hash_specified(self):
72 def test_success_with_no_hash_specified(self):
73 repo = Mock()
73 repo = Mock()
74 ref_type = 'branch'
74 ref_type = 'branch'
75 ref_name = 'master'
75 ref_name = 'master'
76 ref = '{}:{}'.format(ref_type, ref_name)
76 ref = '{}:{}'.format(ref_type, ref_name)
77
77
78 with patch('rhodecode.api.utils._get_ref_hash') \
78 with patch('rhodecode.api.utils._get_ref_hash') \
79 as _get_ref_hash:
79 as _get_ref_hash:
80 _get_ref_hash.return_value = self.commit_hash
80 _get_ref_hash.return_value = self.commit_hash
81 result = utils.resolve_ref_or_error(ref, repo)
81 result = utils.resolve_ref_or_error(ref, repo)
82 _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name)
82 _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name)
83 assert result == '{}:{}'.format(ref, self.commit_hash)
83 assert result == '{}:{}'.format(ref, self.commit_hash)
84
84
85 def test_non_supported_refs(self):
85 def test_non_supported_refs(self):
86 repo = Mock()
86 repo = Mock()
87 ref = 'ancestor:ref'
87 ref = 'ancestor:ref'
88 with pytest.raises(JSONRPCError) as excinfo:
88 with pytest.raises(JSONRPCError) as excinfo:
89 utils.resolve_ref_or_error(ref, repo)
89 utils.resolve_ref_or_error(ref, repo)
90 expected_message = 'The specified ancestor `ref` does not exist'
90 expected_message = 'The specified ancestor `ref` does not exist'
91 assert excinfo.value.message == expected_message
91 assert excinfo.value.message == expected_message
92
92
93 def test_branch_is_not_found(self):
93 def test_branch_is_not_found(self):
94 repo = Mock()
94 repo = Mock()
95 ref = 'branch:non-existing-one'
95 ref = 'branch:non-existing-one'
96 with patch('rhodecode.api.utils._get_ref_hash')\
96 with patch('rhodecode.api.utils._get_ref_hash')\
97 as _get_ref_hash:
97 as _get_ref_hash:
98 _get_ref_hash.side_effect = KeyError()
98 _get_ref_hash.side_effect = KeyError()
99 with pytest.raises(JSONRPCError) as excinfo:
99 with pytest.raises(JSONRPCError) as excinfo:
100 utils.resolve_ref_or_error(ref, repo)
100 utils.resolve_ref_or_error(ref, repo)
101 expected_message = (
101 expected_message = (
102 'The specified branch `non-existing-one` does not exist')
102 'The specified branch `non-existing-one` does not exist')
103 assert excinfo.value.message == expected_message
103 assert excinfo.value.message == expected_message
104
104
105 def test_bookmark_is_not_found(self):
105 def test_bookmark_is_not_found(self):
106 repo = Mock()
106 repo = Mock()
107 ref = 'bookmark:non-existing-one'
107 ref = 'bookmark:non-existing-one'
108 with patch('rhodecode.api.utils._get_ref_hash')\
108 with patch('rhodecode.api.utils._get_ref_hash')\
109 as _get_ref_hash:
109 as _get_ref_hash:
110 _get_ref_hash.side_effect = KeyError()
110 _get_ref_hash.side_effect = KeyError()
111 with pytest.raises(JSONRPCError) as excinfo:
111 with pytest.raises(JSONRPCError) as excinfo:
112 utils.resolve_ref_or_error(ref, repo)
112 utils.resolve_ref_or_error(ref, repo)
113 expected_message = (
113 expected_message = (
114 'The specified bookmark `non-existing-one` does not exist')
114 'The specified bookmark `non-existing-one` does not exist')
115 assert excinfo.value.message == expected_message
115 assert excinfo.value.message == expected_message
116
116
117 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
117 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
118 def test_ref_cannot_be_parsed(self, ref):
118 def test_ref_cannot_be_parsed(self, ref):
119 repo = Mock()
119 repo = Mock()
120 with pytest.raises(JSONRPCError) as excinfo:
120 with pytest.raises(JSONRPCError) as excinfo:
121 utils.resolve_ref_or_error(ref, repo)
121 utils.resolve_ref_or_error(ref, repo)
122 expected_message = (
122 expected_message = (
123 'Ref `{ref}` given in a wrong format. Please check the API'
123 'Ref `{ref}` given in a wrong format. Please check the API'
124 ' documentation for more details'.format(ref=ref)
124 ' documentation for more details'.format(ref=ref)
125 )
125 )
126 assert excinfo.value.message == expected_message
126 assert excinfo.value.message == expected_message
127
127
128
128
129 class TestGetRefHash(object):
129 class TestGetRefHash(object):
130 def setup(self):
130 def setup(self):
131 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
131 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
132 self.bookmark_name = 'test-bookmark'
132 self.bookmark_name = 'test-bookmark'
133
133
134 @pytest.mark.parametrize("alias, branch_name", [
134 @pytest.mark.parametrize("alias, branch_name", [
135 ("git", "master"),
135 ("git", "master"),
136 ("hg", "default")
136 ("hg", "default")
137 ])
137 ])
138 def test_returns_hash_by_branch_name(self, alias, branch_name):
138 def test_returns_hash_by_branch_name(self, alias, branch_name):
139 with patch('rhodecode.model.db.Repository') as repo:
139 with patch('rhodecode.model.db.Repository') as repo:
140 repo.scm_instance().alias = alias
140 repo.scm_instance().alias = alias
141 repo.scm_instance().branches = {branch_name: self.commit_hash}
141 repo.scm_instance().branches = {branch_name: self.commit_hash}
142 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
142 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
143 assert result_hash == self.commit_hash
143 assert result_hash == self.commit_hash
144
144
145 @pytest.mark.parametrize("alias, branch_name", [
145 @pytest.mark.parametrize("alias, branch_name", [
146 ("git", "master"),
146 ("git", "master"),
147 ("hg", "default")
147 ("hg", "default")
148 ])
148 ])
149 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
149 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
150 with patch('rhodecode.model.db.Repository') as repo:
150 with patch('rhodecode.model.db.Repository') as repo:
151 repo.scm_instance().alias = alias
151 repo.scm_instance().alias = alias
152 repo.scm_instance().branches = {}
152 repo.scm_instance().branches = {}
153 with pytest.raises(KeyError):
153 with pytest.raises(KeyError):
154 utils._get_ref_hash(repo, 'branch', branch_name)
154 utils._get_ref_hash(repo, 'branch', branch_name)
155
155
156 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
156 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
157 with patch('rhodecode.model.db.Repository') as repo:
157 with patch('rhodecode.model.db.Repository') as repo:
158 repo.scm_instance().alias = 'hg'
158 repo.scm_instance().alias = 'hg'
159 repo.scm_instance().bookmarks = {
159 repo.scm_instance().bookmarks = {
160 self.bookmark_name: self.commit_hash}
160 self.bookmark_name: self.commit_hash}
161 result_hash = utils._get_ref_hash(
161 result_hash = utils._get_ref_hash(
162 repo, 'bookmark', self.bookmark_name)
162 repo, 'bookmark', self.bookmark_name)
163 assert result_hash == self.commit_hash
163 assert result_hash == self.commit_hash
164
164
165 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
165 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
166 with patch('rhodecode.model.db.Repository') as repo:
166 with patch('rhodecode.model.db.Repository') as repo:
167 repo.scm_instance().alias = 'hg'
167 repo.scm_instance().alias = 'hg'
168 repo.scm_instance().bookmarks = {}
168 repo.scm_instance().bookmarks = {}
169 with pytest.raises(KeyError):
169 with pytest.raises(KeyError):
170 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
170 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
171
171
172 def test_raises_error_when_bookmark_is_specified_for_git(self):
172 def test_raises_error_when_bookmark_is_specified_for_git(self):
173 with patch('rhodecode.model.db.Repository') as repo:
173 with patch('rhodecode.model.db.Repository') as repo:
174 repo.scm_instance().alias = 'git'
174 repo.scm_instance().alias = 'git'
175 repo.scm_instance().bookmarks = {
175 repo.scm_instance().bookmarks = {
176 self.bookmark_name: self.commit_hash}
176 self.bookmark_name: self.commit_hash}
177 with pytest.raises(ValueError):
177 with pytest.raises(ValueError):
178 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
178 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
179
179
180
180
181 class TestUserByNameOrError(object):
181 class TestUserByNameOrError(object):
182 def test_user_found_by_id(self):
182 def test_user_found_by_id(self):
183 fake_user = Mock(id=123)
183 fake_user = Mock(id=123)
184
184
185 patcher = patch('rhodecode.model.user.UserModel.get_user')
185 patcher = patch('rhodecode.model.user.UserModel.get_user')
186 with patcher as get_user:
186 with patcher as get_user:
187 get_user.return_value = fake_user
187 get_user.return_value = fake_user
188
188
189 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
189 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
190 with patcher as get_by_username:
190 with patcher as get_by_username:
191 result = utils.get_user_or_error(123)
191 result = utils.get_user_or_error(123)
192 assert result == fake_user
192 assert result == fake_user
193
193
194 def test_user_not_found_by_id_as_str(self):
194 def test_user_not_found_by_id_as_str(self):
195 fake_user = Mock(id=123)
195 fake_user = Mock(id=123)
196
196
197 patcher = patch('rhodecode.model.user.UserModel.get_user')
197 patcher = patch('rhodecode.model.user.UserModel.get_user')
198 with patcher as get_user:
198 with patcher as get_user:
199 get_user.return_value = fake_user
199 get_user.return_value = fake_user
200 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
200 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
201 with patcher as get_by_username:
201 with patcher as get_by_username:
202 get_by_username.return_value = None
202 get_by_username.return_value = None
203
203
204 with pytest.raises(JSONRPCError):
204 with pytest.raises(JSONRPCError):
205 utils.get_user_or_error('123')
205 utils.get_user_or_error('123')
206
206
207 def test_user_found_by_name(self):
207 def test_user_found_by_name(self):
208 fake_user = Mock(id=123)
208 fake_user = Mock(id=123)
209
209
210 patcher = patch('rhodecode.model.user.UserModel.get_user')
210 patcher = patch('rhodecode.model.user.UserModel.get_user')
211 with patcher as get_user:
211 with patcher as get_user:
212 get_user.return_value = None
212 get_user.return_value = None
213
213
214 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
214 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
215 with patcher as get_by_username:
215 with patcher as get_by_username:
216 get_by_username.return_value = fake_user
216 get_by_username.return_value = fake_user
217
217
218 result = utils.get_user_or_error('test')
218 result = utils.get_user_or_error('test')
219 assert result == fake_user
219 assert result == fake_user
220
220
221 def test_user_not_found_by_id(self):
221 def test_user_not_found_by_id(self):
222 patcher = patch('rhodecode.model.user.UserModel.get_user')
222 patcher = patch('rhodecode.model.user.UserModel.get_user')
223 with patcher as get_user:
223 with patcher as get_user:
224 get_user.return_value = None
224 get_user.return_value = None
225 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
225 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
226 with patcher as get_by_username:
226 with patcher as get_by_username:
227 get_by_username.return_value = None
227 get_by_username.return_value = None
228
228
229 with pytest.raises(JSONRPCError) as excinfo:
229 with pytest.raises(JSONRPCError) as excinfo:
230 utils.get_user_or_error(123)
230 utils.get_user_or_error(123)
231
231
232 expected_message = 'user `123` does not exist'
232 expected_message = 'user `123` does not exist'
233 assert excinfo.value.message == expected_message
233 assert excinfo.value.message == expected_message
234
234
235 def test_user_not_found_by_name(self):
235 def test_user_not_found_by_name(self):
236 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
236 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
237 with patcher as get_by_username:
237 with patcher as get_by_username:
238 get_by_username.return_value = None
238 get_by_username.return_value = None
239 with pytest.raises(JSONRPCError) as excinfo:
239 with pytest.raises(JSONRPCError) as excinfo:
240 utils.get_user_or_error('test')
240 utils.get_user_or_error('test')
241
241
242 expected_message = 'user `test` does not exist'
242 expected_message = 'user `test` does not exist'
243 assert excinfo.value.message == expected_message
243 assert excinfo.value.message == expected_message
244
244
245
245
246 class TestGetCommitDict(object):
246 class TestGetCommitDict(object):
247 @pytest.mark.parametrize('filename, expected', [
247 @pytest.mark.parametrize('filename, expected', [
248 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
248 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
249 (b'sp\xa4cial', u'sp\ufffdcial'),
249 (b'sp\xa4cial', u'sp\ufffdcial'),
250 ])
250 ])
251 def test_decodes_filenames_to_unicode(self, filename, expected):
251 def test_decodes_filenames_to_unicode(self, filename, expected):
252 result = utils._get_commit_dict(filename=filename, op='A')
252 result = utils._get_commit_dict(filename=filename, op='A')
253 assert result['filename'] == expected
253 assert result['filename'] == expected
254
254
255
255
256 class TestRepoAccess(object):
256 class TestRepoAccess(object):
257 def setup_method(self, method):
257 def setup_method(self, method):
258
258
259 self.admin_perm_patch = patch(
259 self.admin_perm_patch = patch(
260 'rhodecode.api.utils.HasPermissionAnyApi')
260 'rhodecode.api.utils.HasPermissionAnyApi')
261 self.repo_perm_patch = patch(
261 self.repo_perm_patch = patch(
262 'rhodecode.api.utils.HasRepoPermissionAnyApi')
262 'rhodecode.api.utils.HasRepoPermissionAnyApi')
263
263
264 def test_has_superadmin_permission_checks_for_admin(self):
264 def test_has_superadmin_permission_checks_for_admin(self):
265 admin_mock = Mock()
265 admin_mock = Mock()
266 with self.admin_perm_patch as amock:
266 with self.admin_perm_patch as amock:
267 amock.return_value = admin_mock
267 amock.return_value = admin_mock
268 assert utils.has_superadmin_permission('fake_user')
268 assert utils.has_superadmin_permission('fake_user')
269 amock.assert_called_once_with('hg.admin')
269 amock.assert_called_once_with('hg.admin')
270
270
271 admin_mock.assert_called_once_with(user='fake_user')
271 admin_mock.assert_called_once_with(user='fake_user')
272
272
273 def test_has_repo_permissions_checks_for_repo_access(self):
273 def test_has_repo_permissions_checks_for_repo_access(self):
274 repo_mock = Mock()
274 repo_mock = Mock()
275 fake_repo = Mock()
275 fake_repo = Mock()
276 with self.repo_perm_patch as rmock:
276 with self.repo_perm_patch as rmock:
277 rmock.return_value = repo_mock
277 rmock.return_value = repo_mock
278 assert utils.validate_repo_permissions(
278 assert utils.validate_repo_permissions(
279 'fake_user', 'fake_repo_id', fake_repo,
279 'fake_user', 'fake_repo_id', fake_repo,
280 ['perm1', 'perm2'])
280 ['perm1', 'perm2'])
281 rmock.assert_called_once_with(*['perm1', 'perm2'])
281 rmock.assert_called_once_with(*['perm1', 'perm2'])
282
282
283 repo_mock.assert_called_once_with(
283 repo_mock.assert_called_once_with(
284 user='fake_user', repo_name=fake_repo.repo_name)
284 user='fake_user', repo_name=fake_repo.repo_name)
285
285
286 def test_has_repo_permissions_raises_not_found(self):
286 def test_has_repo_permissions_raises_not_found(self):
287 repo_mock = Mock(return_value=False)
287 repo_mock = Mock(return_value=False)
288 fake_repo = Mock()
288 fake_repo = Mock()
289 with self.repo_perm_patch as rmock:
289 with self.repo_perm_patch as rmock:
290 rmock.return_value = repo_mock
290 rmock.return_value = repo_mock
291 with pytest.raises(JSONRPCError) as excinfo:
291 with pytest.raises(JSONRPCError) as excinfo:
292 utils.validate_repo_permissions(
292 utils.validate_repo_permissions(
293 'fake_user', 'fake_repo_id', fake_repo, 'perms')
293 'fake_user', 'fake_repo_id', fake_repo, 'perms')
294 assert 'fake_repo_id' in excinfo
294 assert 'fake_repo_id' in excinfo
@@ -1,106 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import random
22 import random
23
23
24 from rhodecode.api.utils import get_origin
24 from rhodecode.api.utils import get_origin
25 from rhodecode.lib.ext_json import json
25 from rhodecode.lib.ext_json import json
26
26
27
27
28 API_URL = '/_admin/api'
28 API_URL = '/_admin/api'
29
29
30
30
31 def assert_ok(id_, expected, given):
31 def assert_ok(id_, expected, given):
32 expected = jsonify({
32 expected = jsonify({
33 'id': id_,
33 'id': id_,
34 'error': None,
34 'error': None,
35 'result': expected
35 'result': expected
36 })
36 })
37 given = json.loads(given)
37 given = json.loads(given)
38 assert expected == given
38 assert expected == given
39
39
40
40
41 def assert_error(id_, expected, given):
41 def assert_error(id_, expected, given):
42 expected = jsonify({
42 expected = jsonify({
43 'id': id_,
43 'id': id_,
44 'error': expected,
44 'error': expected,
45 'result': None
45 'result': None
46 })
46 })
47 given = json.loads(given)
47 given = json.loads(given)
48 assert expected == given
48 assert expected == given
49
49
50
50
51 def jsonify(obj):
51 def jsonify(obj):
52 return json.loads(json.dumps(obj))
52 return json.loads(json.dumps(obj))
53
53
54
54
55 def build_data(apikey, method, **kw):
55 def build_data(apikey, method, **kw):
56 """
56 """
57 Builds API data with given random ID
57 Builds API data with given random ID
58
58
59 :param random_id:
59 :param random_id:
60 """
60 """
61 random_id = random.randrange(1, 9999)
61 random_id = random.randrange(1, 9999)
62 return random_id, json.dumps({
62 return random_id, json.dumps({
63 "id": random_id,
63 "id": random_id,
64 "api_key": apikey,
64 "api_key": apikey,
65 "method": method,
65 "method": method,
66 "args": kw
66 "args": kw
67 })
67 })
68
68
69
69
70 def api_call(app, params, status=None):
70 def api_call(app, params, status=None):
71 response = app.post(
71 response = app.post(
72 API_URL, content_type='application/json', params=params, status=status)
72 API_URL, content_type='application/json', params=params, status=status)
73 return response
73 return response
74
74
75
75
76 def crash(*args, **kwargs):
76 def crash(*args, **kwargs):
77 raise Exception('Total Crash !')
77 raise Exception('Total Crash !')
78
78
79
79
80 def expected_permissions(object_with_permissions):
80 def expected_permissions(object_with_permissions):
81 """
81 """
82 Returns the expected permissions structure for the given object.
82 Returns the expected permissions structure for the given object.
83
83
84 The object is expected to be a `Repository`, `RepositoryGroup`,
84 The object is expected to be a `Repository`, `RepositoryGroup`,
85 or `UserGroup`. They all implement the same permission handling
85 or `UserGroup`. They all implement the same permission handling
86 API.
86 API.
87 """
87 """
88 permissions = []
88 permissions = []
89 for _user in object_with_permissions.permissions():
89 for _user in object_with_permissions.permissions():
90 user_data = {
90 user_data = {
91 'name': _user.username,
91 'name': _user.username,
92 'permission': _user.permission,
92 'permission': _user.permission,
93 'origin': get_origin(_user),
93 'origin': get_origin(_user),
94 'type': "user",
94 'type': "user",
95 }
95 }
96 permissions.append(user_data)
96 permissions.append(user_data)
97
97
98 for _user_group in object_with_permissions.permission_user_groups():
98 for _user_group in object_with_permissions.permission_user_groups():
99 user_group_data = {
99 user_group_data = {
100 'name': _user_group.users_group_name,
100 'name': _user_group.users_group_name,
101 'permission': _user_group.permission,
101 'permission': _user_group.permission,
102 'origin': get_origin(_user_group),
102 'origin': get_origin(_user_group),
103 'type': "user_group",
103 'type': "user_group",
104 }
104 }
105 permissions.append(user_group_data)
105 permissions.append(user_group_data)
106 return permissions
106 return permissions
@@ -1,442 +1,442 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2017 RhodeCode GmbH
3 # Copyright (C) 2014-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 JSON RPC utils
22 JSON RPC utils
23 """
23 """
24
24
25 import collections
25 import collections
26 import logging
26 import logging
27
27
28 from rhodecode.api.exc import JSONRPCError
28 from rhodecode.api.exc import JSONRPCError
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
31 from rhodecode.lib.utils import safe_unicode
31 from rhodecode.lib.utils import safe_unicode
32 from rhodecode.lib.vcs.exceptions import RepositoryError
32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 from rhodecode.controllers.utils import get_commit_from_ref_name
33 from rhodecode.controllers.utils import get_commit_from_ref_name
34 from rhodecode.lib.utils2 import str2bool
34 from rhodecode.lib.utils2 import str2bool
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class OAttr(object):
39 class OAttr(object):
40 """
40 """
41 Special Option that defines other attribute, and can default to them
41 Special Option that defines other attribute, and can default to them
42
42
43 Example::
43 Example::
44
44
45 def test(apiuser, userid=Optional(OAttr('apiuser')):
45 def test(apiuser, userid=Optional(OAttr('apiuser')):
46 user = Optional.extract(userid, evaluate_locals=local())
46 user = Optional.extract(userid, evaluate_locals=local())
47 #if we pass in userid, we get it, else it will default to apiuser
47 #if we pass in userid, we get it, else it will default to apiuser
48 #attribute
48 #attribute
49 """
49 """
50
50
51 def __init__(self, attr_name):
51 def __init__(self, attr_name):
52 self.attr_name = attr_name
52 self.attr_name = attr_name
53
53
54 def __repr__(self):
54 def __repr__(self):
55 return '<OptionalAttr:%s>' % self.attr_name
55 return '<OptionalAttr:%s>' % self.attr_name
56
56
57 def __call__(self):
57 def __call__(self):
58 return self
58 return self
59
59
60
60
61 class Optional(object):
61 class Optional(object):
62 """
62 """
63 Defines an optional parameter::
63 Defines an optional parameter::
64
64
65 param = param.getval() if isinstance(param, Optional) else param
65 param = param.getval() if isinstance(param, Optional) else param
66 param = param() if isinstance(param, Optional) else param
66 param = param() if isinstance(param, Optional) else param
67
67
68 is equivalent of::
68 is equivalent of::
69
69
70 param = Optional.extract(param)
70 param = Optional.extract(param)
71
71
72 """
72 """
73
73
74 def __init__(self, type_):
74 def __init__(self, type_):
75 self.type_ = type_
75 self.type_ = type_
76
76
77 def __repr__(self):
77 def __repr__(self):
78 return '<Optional:%s>' % self.type_.__repr__()
78 return '<Optional:%s>' % self.type_.__repr__()
79
79
80 def __call__(self):
80 def __call__(self):
81 return self.getval()
81 return self.getval()
82
82
83 def getval(self, evaluate_locals=None):
83 def getval(self, evaluate_locals=None):
84 """
84 """
85 returns value from this Optional instance
85 returns value from this Optional instance
86 """
86 """
87 if isinstance(self.type_, OAttr):
87 if isinstance(self.type_, OAttr):
88 param_name = self.type_.attr_name
88 param_name = self.type_.attr_name
89 if evaluate_locals:
89 if evaluate_locals:
90 return evaluate_locals[param_name]
90 return evaluate_locals[param_name]
91 # use params name
91 # use params name
92 return param_name
92 return param_name
93 return self.type_
93 return self.type_
94
94
95 @classmethod
95 @classmethod
96 def extract(cls, val, evaluate_locals=None, binary=None):
96 def extract(cls, val, evaluate_locals=None, binary=None):
97 """
97 """
98 Extracts value from Optional() instance
98 Extracts value from Optional() instance
99
99
100 :param val:
100 :param val:
101 :return: original value if it's not Optional instance else
101 :return: original value if it's not Optional instance else
102 value of instance
102 value of instance
103 """
103 """
104 if isinstance(val, cls):
104 if isinstance(val, cls):
105 val = val.getval(evaluate_locals)
105 val = val.getval(evaluate_locals)
106
106
107 if binary:
107 if binary:
108 val = str2bool(val)
108 val = str2bool(val)
109
109
110 return val
110 return val
111
111
112
112
113 def parse_args(cli_args, key_prefix=''):
113 def parse_args(cli_args, key_prefix=''):
114 from rhodecode.lib.utils2 import (escape_split)
114 from rhodecode.lib.utils2 import (escape_split)
115 kwargs = collections.defaultdict(dict)
115 kwargs = collections.defaultdict(dict)
116 for el in escape_split(cli_args, ','):
116 for el in escape_split(cli_args, ','):
117 kv = escape_split(el, '=', 1)
117 kv = escape_split(el, '=', 1)
118 if len(kv) == 2:
118 if len(kv) == 2:
119 k, v = kv
119 k, v = kv
120 kwargs[key_prefix + k] = v
120 kwargs[key_prefix + k] = v
121 return kwargs
121 return kwargs
122
122
123
123
124 def get_origin(obj):
124 def get_origin(obj):
125 """
125 """
126 Get origin of permission from object.
126 Get origin of permission from object.
127
127
128 :param obj:
128 :param obj:
129 """
129 """
130 origin = 'permission'
130 origin = 'permission'
131
131
132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
133 # admin and owner case, maybe we should use dual string ?
133 # admin and owner case, maybe we should use dual string ?
134 origin = 'owner'
134 origin = 'owner'
135 elif getattr(obj, 'owner_row', ''):
135 elif getattr(obj, 'owner_row', ''):
136 origin = 'owner'
136 origin = 'owner'
137 elif getattr(obj, 'admin_row', ''):
137 elif getattr(obj, 'admin_row', ''):
138 origin = 'super-admin'
138 origin = 'super-admin'
139 return origin
139 return origin
140
140
141
141
142 def store_update(updates, attr, name):
142 def store_update(updates, attr, name):
143 """
143 """
144 Stores param in updates dict if it's not instance of Optional
144 Stores param in updates dict if it's not instance of Optional
145 allows easy updates of passed in params
145 allows easy updates of passed in params
146 """
146 """
147 if not isinstance(attr, Optional):
147 if not isinstance(attr, Optional):
148 updates[name] = attr
148 updates[name] = attr
149
149
150
150
151 def has_superadmin_permission(apiuser):
151 def has_superadmin_permission(apiuser):
152 """
152 """
153 Return True if apiuser is admin or return False
153 Return True if apiuser is admin or return False
154
154
155 :param apiuser:
155 :param apiuser:
156 """
156 """
157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
158 return True
158 return True
159 return False
159 return False
160
160
161
161
162 def validate_repo_permissions(apiuser, repoid, repo, perms):
162 def validate_repo_permissions(apiuser, repoid, repo, perms):
163 """
163 """
164 Raise JsonRPCError if apiuser is not authorized or return True
164 Raise JsonRPCError if apiuser is not authorized or return True
165
165
166 :param apiuser:
166 :param apiuser:
167 :param repoid:
167 :param repoid:
168 :param repo:
168 :param repo:
169 :param perms:
169 :param perms:
170 """
170 """
171 if not HasRepoPermissionAnyApi(*perms)(
171 if not HasRepoPermissionAnyApi(*perms)(
172 user=apiuser, repo_name=repo.repo_name):
172 user=apiuser, repo_name=repo.repo_name):
173 raise JSONRPCError(
173 raise JSONRPCError(
174 'repository `%s` does not exist' % repoid)
174 'repository `%s` does not exist' % repoid)
175
175
176 return True
176 return True
177
177
178
178
179 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
179 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
180 """
180 """
181 Raise JsonRPCError if apiuser is not authorized or return True
181 Raise JsonRPCError if apiuser is not authorized or return True
182
182
183 :param apiuser:
183 :param apiuser:
184 :param repogroupid: just the id of repository group
184 :param repogroupid: just the id of repository group
185 :param repo_group: instance of repo_group
185 :param repo_group: instance of repo_group
186 :param perms:
186 :param perms:
187 """
187 """
188 if not HasRepoGroupPermissionAnyApi(*perms)(
188 if not HasRepoGroupPermissionAnyApi(*perms)(
189 user=apiuser, group_name=repo_group.group_name):
189 user=apiuser, group_name=repo_group.group_name):
190 raise JSONRPCError(
190 raise JSONRPCError(
191 'repository group `%s` does not exist' % repogroupid)
191 'repository group `%s` does not exist' % repogroupid)
192
192
193 return True
193 return True
194
194
195
195
196 def validate_set_owner_permissions(apiuser, owner):
196 def validate_set_owner_permissions(apiuser, owner):
197 if isinstance(owner, Optional):
197 if isinstance(owner, Optional):
198 owner = get_user_or_error(apiuser.user_id)
198 owner = get_user_or_error(apiuser.user_id)
199 else:
199 else:
200 if has_superadmin_permission(apiuser):
200 if has_superadmin_permission(apiuser):
201 owner = get_user_or_error(owner)
201 owner = get_user_or_error(owner)
202 else:
202 else:
203 # forbid setting owner for non-admins
203 # forbid setting owner for non-admins
204 raise JSONRPCError(
204 raise JSONRPCError(
205 'Only RhodeCode super-admin can specify `owner` param')
205 'Only RhodeCode super-admin can specify `owner` param')
206 return owner
206 return owner
207
207
208
208
209 def get_user_or_error(userid):
209 def get_user_or_error(userid):
210 """
210 """
211 Get user by id or name or return JsonRPCError if not found
211 Get user by id or name or return JsonRPCError if not found
212
212
213 :param userid:
213 :param userid:
214 """
214 """
215 from rhodecode.model.user import UserModel
215 from rhodecode.model.user import UserModel
216 user_model = UserModel()
216 user_model = UserModel()
217
217
218 if isinstance(userid, (int, long)):
218 if isinstance(userid, (int, long)):
219 try:
219 try:
220 user = user_model.get_user(userid)
220 user = user_model.get_user(userid)
221 except ValueError:
221 except ValueError:
222 user = None
222 user = None
223 else:
223 else:
224 user = user_model.get_by_username(userid)
224 user = user_model.get_by_username(userid)
225
225
226 if user is None:
226 if user is None:
227 raise JSONRPCError(
227 raise JSONRPCError(
228 'user `%s` does not exist' % (userid,))
228 'user `%s` does not exist' % (userid,))
229 return user
229 return user
230
230
231
231
232 def get_repo_or_error(repoid):
232 def get_repo_or_error(repoid):
233 """
233 """
234 Get repo by id or name or return JsonRPCError if not found
234 Get repo by id or name or return JsonRPCError if not found
235
235
236 :param repoid:
236 :param repoid:
237 """
237 """
238 from rhodecode.model.repo import RepoModel
238 from rhodecode.model.repo import RepoModel
239 repo_model = RepoModel()
239 repo_model = RepoModel()
240
240
241 if isinstance(repoid, (int, long)):
241 if isinstance(repoid, (int, long)):
242 try:
242 try:
243 repo = repo_model.get_repo(repoid)
243 repo = repo_model.get_repo(repoid)
244 except ValueError:
244 except ValueError:
245 repo = None
245 repo = None
246 else:
246 else:
247 repo = repo_model.get_by_repo_name(repoid)
247 repo = repo_model.get_by_repo_name(repoid)
248
248
249 if repo is None:
249 if repo is None:
250 raise JSONRPCError(
250 raise JSONRPCError(
251 'repository `%s` does not exist' % (repoid,))
251 'repository `%s` does not exist' % (repoid,))
252 return repo
252 return repo
253
253
254
254
255 def get_repo_group_or_error(repogroupid):
255 def get_repo_group_or_error(repogroupid):
256 """
256 """
257 Get repo group by id or name or return JsonRPCError if not found
257 Get repo group by id or name or return JsonRPCError if not found
258
258
259 :param repogroupid:
259 :param repogroupid:
260 """
260 """
261 from rhodecode.model.repo_group import RepoGroupModel
261 from rhodecode.model.repo_group import RepoGroupModel
262 repo_group_model = RepoGroupModel()
262 repo_group_model = RepoGroupModel()
263
263
264 if isinstance(repogroupid, (int, long)):
264 if isinstance(repogroupid, (int, long)):
265 try:
265 try:
266 repo_group = repo_group_model._get_repo_group(repogroupid)
266 repo_group = repo_group_model._get_repo_group(repogroupid)
267 except ValueError:
267 except ValueError:
268 repo_group = None
268 repo_group = None
269 else:
269 else:
270 repo_group = repo_group_model.get_by_group_name(repogroupid)
270 repo_group = repo_group_model.get_by_group_name(repogroupid)
271
271
272 if repo_group is None:
272 if repo_group is None:
273 raise JSONRPCError(
273 raise JSONRPCError(
274 'repository group `%s` does not exist' % (repogroupid,))
274 'repository group `%s` does not exist' % (repogroupid,))
275 return repo_group
275 return repo_group
276
276
277
277
278 def get_user_group_or_error(usergroupid):
278 def get_user_group_or_error(usergroupid):
279 """
279 """
280 Get user group by id or name or return JsonRPCError if not found
280 Get user group by id or name or return JsonRPCError if not found
281
281
282 :param usergroupid:
282 :param usergroupid:
283 """
283 """
284 from rhodecode.model.user_group import UserGroupModel
284 from rhodecode.model.user_group import UserGroupModel
285 user_group_model = UserGroupModel()
285 user_group_model = UserGroupModel()
286
286
287 if isinstance(usergroupid, (int, long)):
287 if isinstance(usergroupid, (int, long)):
288 try:
288 try:
289 user_group = user_group_model.get_group(usergroupid)
289 user_group = user_group_model.get_group(usergroupid)
290 except ValueError:
290 except ValueError:
291 user_group = None
291 user_group = None
292 else:
292 else:
293 user_group = user_group_model.get_by_name(usergroupid)
293 user_group = user_group_model.get_by_name(usergroupid)
294
294
295 if user_group is None:
295 if user_group is None:
296 raise JSONRPCError(
296 raise JSONRPCError(
297 'user group `%s` does not exist' % (usergroupid,))
297 'user group `%s` does not exist' % (usergroupid,))
298 return user_group
298 return user_group
299
299
300
300
301 def get_perm_or_error(permid, prefix=None):
301 def get_perm_or_error(permid, prefix=None):
302 """
302 """
303 Get permission by id or name or return JsonRPCError if not found
303 Get permission by id or name or return JsonRPCError if not found
304
304
305 :param permid:
305 :param permid:
306 """
306 """
307 from rhodecode.model.permission import PermissionModel
307 from rhodecode.model.permission import PermissionModel
308
308
309 perm = PermissionModel.cls.get_by_key(permid)
309 perm = PermissionModel.cls.get_by_key(permid)
310 if perm is None:
310 if perm is None:
311 raise JSONRPCError('permission `%s` does not exist' % (permid,))
311 raise JSONRPCError('permission `%s` does not exist' % (permid,))
312 if prefix:
312 if prefix:
313 if not perm.permission_name.startswith(prefix):
313 if not perm.permission_name.startswith(prefix):
314 raise JSONRPCError('permission `%s` is invalid, '
314 raise JSONRPCError('permission `%s` is invalid, '
315 'should start with %s' % (permid, prefix))
315 'should start with %s' % (permid, prefix))
316 return perm
316 return perm
317
317
318
318
319 def get_gist_or_error(gistid):
319 def get_gist_or_error(gistid):
320 """
320 """
321 Get gist by id or gist_access_id or return JsonRPCError if not found
321 Get gist by id or gist_access_id or return JsonRPCError if not found
322
322
323 :param gistid:
323 :param gistid:
324 """
324 """
325 from rhodecode.model.gist import GistModel
325 from rhodecode.model.gist import GistModel
326
326
327 gist = GistModel.cls.get_by_access_id(gistid)
327 gist = GistModel.cls.get_by_access_id(gistid)
328 if gist is None:
328 if gist is None:
329 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
329 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
330 return gist
330 return gist
331
331
332
332
333 def get_pull_request_or_error(pullrequestid):
333 def get_pull_request_or_error(pullrequestid):
334 """
334 """
335 Get pull request by id or return JsonRPCError if not found
335 Get pull request by id or return JsonRPCError if not found
336
336
337 :param pullrequestid:
337 :param pullrequestid:
338 """
338 """
339 from rhodecode.model.pull_request import PullRequestModel
339 from rhodecode.model.pull_request import PullRequestModel
340
340
341 try:
341 try:
342 pull_request = PullRequestModel().get(int(pullrequestid))
342 pull_request = PullRequestModel().get(int(pullrequestid))
343 except ValueError:
343 except ValueError:
344 raise JSONRPCError('pullrequestid must be an integer')
344 raise JSONRPCError('pullrequestid must be an integer')
345 if not pull_request:
345 if not pull_request:
346 raise JSONRPCError('pull request `%s` does not exist' % (
346 raise JSONRPCError('pull request `%s` does not exist' % (
347 pullrequestid,))
347 pullrequestid,))
348 return pull_request
348 return pull_request
349
349
350
350
351 def build_commit_data(commit, detail_level):
351 def build_commit_data(commit, detail_level):
352 parsed_diff = []
352 parsed_diff = []
353 if detail_level == 'extended':
353 if detail_level == 'extended':
354 for f in commit.added:
354 for f in commit.added:
355 parsed_diff.append(_get_commit_dict(filename=f.path, op='A'))
355 parsed_diff.append(_get_commit_dict(filename=f.path, op='A'))
356 for f in commit.changed:
356 for f in commit.changed:
357 parsed_diff.append(_get_commit_dict(filename=f.path, op='M'))
357 parsed_diff.append(_get_commit_dict(filename=f.path, op='M'))
358 for f in commit.removed:
358 for f in commit.removed:
359 parsed_diff.append(_get_commit_dict(filename=f.path, op='D'))
359 parsed_diff.append(_get_commit_dict(filename=f.path, op='D'))
360
360
361 elif detail_level == 'full':
361 elif detail_level == 'full':
362 from rhodecode.lib.diffs import DiffProcessor
362 from rhodecode.lib.diffs import DiffProcessor
363 diff_processor = DiffProcessor(commit.diff())
363 diff_processor = DiffProcessor(commit.diff())
364 for dp in diff_processor.prepare():
364 for dp in diff_processor.prepare():
365 del dp['stats']['ops']
365 del dp['stats']['ops']
366 _stats = dp['stats']
366 _stats = dp['stats']
367 parsed_diff.append(_get_commit_dict(
367 parsed_diff.append(_get_commit_dict(
368 filename=dp['filename'], op=dp['operation'],
368 filename=dp['filename'], op=dp['operation'],
369 new_revision=dp['new_revision'],
369 new_revision=dp['new_revision'],
370 old_revision=dp['old_revision'],
370 old_revision=dp['old_revision'],
371 raw_diff=dp['raw_diff'], stats=_stats))
371 raw_diff=dp['raw_diff'], stats=_stats))
372
372
373 return parsed_diff
373 return parsed_diff
374
374
375
375
376 def get_commit_or_error(ref, repo):
376 def get_commit_or_error(ref, repo):
377 try:
377 try:
378 ref_type, _, ref_hash = ref.split(':')
378 ref_type, _, ref_hash = ref.split(':')
379 except ValueError:
379 except ValueError:
380 raise JSONRPCError(
380 raise JSONRPCError(
381 'Ref `{ref}` given in a wrong format. Please check the API'
381 'Ref `{ref}` given in a wrong format. Please check the API'
382 ' documentation for more details'.format(ref=ref))
382 ' documentation for more details'.format(ref=ref))
383 try:
383 try:
384 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
384 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
385 # once get_commit supports ref_types
385 # once get_commit supports ref_types
386 return get_commit_from_ref_name(repo, ref_hash)
386 return get_commit_from_ref_name(repo, ref_hash)
387 except RepositoryError:
387 except RepositoryError:
388 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
388 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
389
389
390
390
391 def resolve_ref_or_error(ref, repo):
391 def resolve_ref_or_error(ref, repo):
392 def _parse_ref(type_, name, hash_=None):
392 def _parse_ref(type_, name, hash_=None):
393 return type_, name, hash_
393 return type_, name, hash_
394
394
395 try:
395 try:
396 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
396 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
397 except TypeError:
397 except TypeError:
398 raise JSONRPCError(
398 raise JSONRPCError(
399 'Ref `{ref}` given in a wrong format. Please check the API'
399 'Ref `{ref}` given in a wrong format. Please check the API'
400 ' documentation for more details'.format(ref=ref))
400 ' documentation for more details'.format(ref=ref))
401
401
402 try:
402 try:
403 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
403 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
404 except (KeyError, ValueError):
404 except (KeyError, ValueError):
405 raise JSONRPCError(
405 raise JSONRPCError(
406 'The specified {type} `{name}` does not exist'.format(
406 'The specified {type} `{name}` does not exist'.format(
407 type=ref_type, name=ref_name))
407 type=ref_type, name=ref_name))
408
408
409 return ':'.join([ref_type, ref_name, ref_hash])
409 return ':'.join([ref_type, ref_name, ref_hash])
410
410
411
411
412 def _get_commit_dict(
412 def _get_commit_dict(
413 filename, op, new_revision=None, old_revision=None,
413 filename, op, new_revision=None, old_revision=None,
414 raw_diff=None, stats=None):
414 raw_diff=None, stats=None):
415 if stats is None:
415 if stats is None:
416 stats = {
416 stats = {
417 "added": None,
417 "added": None,
418 "binary": None,
418 "binary": None,
419 "deleted": None
419 "deleted": None
420 }
420 }
421 return {
421 return {
422 "filename": safe_unicode(filename),
422 "filename": safe_unicode(filename),
423 "op": op,
423 "op": op,
424
424
425 # extra details
425 # extra details
426 "new_revision": new_revision,
426 "new_revision": new_revision,
427 "old_revision": old_revision,
427 "old_revision": old_revision,
428
428
429 "raw_diff": raw_diff,
429 "raw_diff": raw_diff,
430 "stats": stats
430 "stats": stats
431 }
431 }
432
432
433
433
434 # TODO: mikhail: Think about moving this function to some library
434 # TODO: mikhail: Think about moving this function to some library
435 def _get_ref_hash(repo, type_, name):
435 def _get_ref_hash(repo, type_, name):
436 vcs_repo = repo.scm_instance()
436 vcs_repo = repo.scm_instance()
437 if type_ == 'branch' and vcs_repo.alias in ('hg', 'git'):
437 if type_ == 'branch' and vcs_repo.alias in ('hg', 'git'):
438 return vcs_repo.branches[name]
438 return vcs_repo.branches[name]
439 elif type_ == 'bookmark' and vcs_repo.alias == 'hg':
439 elif type_ == 'bookmark' and vcs_repo.alias == 'hg':
440 return vcs_repo.bookmarks[name]
440 return vcs_repo.bookmarks[name]
441 else:
441 else:
442 raise ValueError()
442 raise ValueError()
@@ -1,19 +1,19 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2015-2017 RhodeCode GmbH
3 # Copyright (C) 2015-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,102 +1,102 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 NOTE:
23 NOTE:
24 Place for deprecated APIs here, if a call needs to be deprecated, please
24 Place for deprecated APIs here, if a call needs to be deprecated, please
25 put it here, and point to a new version
25 put it here, and point to a new version
26 """
26 """
27 import logging
27 import logging
28
28
29 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method
29 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method
30 from rhodecode.api.utils import Optional, OAttr
30 from rhodecode.api.utils import Optional, OAttr
31
31
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 # permission check inside
36 # permission check inside
37 @jsonrpc_method()
37 @jsonrpc_method()
38 @jsonrpc_deprecated_method(
38 @jsonrpc_deprecated_method(
39 use_method='comment_commit', deprecated_at_version='3.4.0')
39 use_method='comment_commit', deprecated_at_version='3.4.0')
40 def changeset_comment(request, apiuser, repoid, revision, message,
40 def changeset_comment(request, apiuser, repoid, revision, message,
41 userid=Optional(OAttr('apiuser')),
41 userid=Optional(OAttr('apiuser')),
42 status=Optional(None)):
42 status=Optional(None)):
43 """
43 """
44 Set a changeset comment, and optionally change the status of the
44 Set a changeset comment, and optionally change the status of the
45 changeset.
45 changeset.
46
46
47 This command can only be run using an |authtoken| with admin
47 This command can only be run using an |authtoken| with admin
48 permissions on the |repo|.
48 permissions on the |repo|.
49
49
50 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
51 :type apiuser: AuthUser
51 :type apiuser: AuthUser
52 :param repoid: Set the repository name or repository ID.
52 :param repoid: Set the repository name or repository ID.
53 :type repoid: str or int
53 :type repoid: str or int
54 :param revision: Specify the revision for which to set a comment.
54 :param revision: Specify the revision for which to set a comment.
55 :type revision: str
55 :type revision: str
56 :param message: The comment text.
56 :param message: The comment text.
57 :type message: str
57 :type message: str
58 :param userid: Set the user name of the comment creator.
58 :param userid: Set the user name of the comment creator.
59 :type userid: Optional(str or int)
59 :type userid: Optional(str or int)
60 :param status: Set the comment status. The following are valid options:
60 :param status: Set the comment status. The following are valid options:
61 * not_reviewed
61 * not_reviewed
62 * approved
62 * approved
63 * rejected
63 * rejected
64 * under_review
64 * under_review
65 :type status: str
65 :type status: str
66
66
67 Example error output:
67 Example error output:
68
68
69 .. code-block:: json
69 .. code-block:: json
70
70
71 {
71 {
72 "id" : <id_given_in_input>,
72 "id" : <id_given_in_input>,
73 "result" : {
73 "result" : {
74 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
74 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
75 "status_change": null or <status>,
75 "status_change": null or <status>,
76 "success": true
76 "success": true
77 },
77 },
78 "error" : null
78 "error" : null
79 }
79 }
80
80
81 """
81 """
82 from .repo_api import comment_commit
82 from .repo_api import comment_commit
83
83
84 return comment_commit(request=request,
84 return comment_commit(request=request,
85 apiuser=apiuser, repoid=repoid, commit_id=revision,
85 apiuser=apiuser, repoid=repoid, commit_id=revision,
86 message=message, userid=userid, status=status)
86 message=message, userid=userid, status=status)
87
87
88
88
89 @jsonrpc_method()
89 @jsonrpc_method()
90 @jsonrpc_deprecated_method(
90 @jsonrpc_deprecated_method(
91 use_method='get_ip', deprecated_at_version='4.0.0')
91 use_method='get_ip', deprecated_at_version='4.0.0')
92 def show_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
92 def show_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
93 from .server_api import get_ip
93 from .server_api import get_ip
94 return get_ip(request=request, apiuser=apiuser, userid=userid)
94 return get_ip(request=request, apiuser=apiuser, userid=userid)
95
95
96
96
97 @jsonrpc_method()
97 @jsonrpc_method()
98 @jsonrpc_deprecated_method(
98 @jsonrpc_deprecated_method(
99 use_method='get_user_locks', deprecated_at_version='4.0.0')
99 use_method='get_user_locks', deprecated_at_version='4.0.0')
100 def get_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
100 def get_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
101 from .user_api import get_user_locks
101 from .user_api import get_user_locks
102 return get_user_locks(request=request, apiuser=apiuser, userid=userid) No newline at end of file
102 return get_user_locks(request=request, apiuser=apiuser, userid=userid)
@@ -1,255 +1,255 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import time
23 import time
24
24
25 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.exc import JSONRPCValidationError
26 from rhodecode.api.exc import JSONRPCValidationError
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 Optional, OAttr, get_gist_or_error, get_user_or_error,
28 Optional, OAttr, get_gist_or_error, get_user_or_error,
29 has_superadmin_permission)
29 has_superadmin_permission)
30 from rhodecode.model.db import Session, or_
30 from rhodecode.model.db import Session, or_
31 from rhodecode.model.gist import Gist, GistModel
31 from rhodecode.model.gist import Gist, GistModel
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 @jsonrpc_method()
36 @jsonrpc_method()
37 def get_gist(request, apiuser, gistid, content=Optional(False)):
37 def get_gist(request, apiuser, gistid, content=Optional(False)):
38 """
38 """
39 Get the specified gist, based on the gist ID.
39 Get the specified gist, based on the gist ID.
40
40
41 :param apiuser: This is filled automatically from the |authtoken|.
41 :param apiuser: This is filled automatically from the |authtoken|.
42 :type apiuser: AuthUser
42 :type apiuser: AuthUser
43 :param gistid: Set the id of the private or public gist
43 :param gistid: Set the id of the private or public gist
44 :type gistid: str
44 :type gistid: str
45 :param content: Return the gist content. Default is false.
45 :param content: Return the gist content. Default is false.
46 :type content: Optional(bool)
46 :type content: Optional(bool)
47 """
47 """
48
48
49 gist = get_gist_or_error(gistid)
49 gist = get_gist_or_error(gistid)
50 content = Optional.extract(content)
50 content = Optional.extract(content)
51 if not has_superadmin_permission(apiuser):
51 if not has_superadmin_permission(apiuser):
52 if gist.gist_owner != apiuser.user_id:
52 if gist.gist_owner != apiuser.user_id:
53 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
53 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
54 data = gist.get_api_data()
54 data = gist.get_api_data()
55 if content:
55 if content:
56 from rhodecode.model.gist import GistModel
56 from rhodecode.model.gist import GistModel
57 rev, gist_files = GistModel().get_gist_files(gistid)
57 rev, gist_files = GistModel().get_gist_files(gistid)
58 data['content'] = dict([(x.path, x.content) for x in gist_files])
58 data['content'] = dict([(x.path, x.content) for x in gist_files])
59 return data
59 return data
60
60
61
61
62 @jsonrpc_method()
62 @jsonrpc_method()
63 def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))):
63 def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))):
64 """
64 """
65 Get all gists for given user. If userid is empty returned gists
65 Get all gists for given user. If userid is empty returned gists
66 are for user who called the api
66 are for user who called the api
67
67
68 :param apiuser: This is filled automatically from the |authtoken|.
68 :param apiuser: This is filled automatically from the |authtoken|.
69 :type apiuser: AuthUser
69 :type apiuser: AuthUser
70 :param userid: user to get gists for
70 :param userid: user to get gists for
71 :type userid: Optional(str or int)
71 :type userid: Optional(str or int)
72 """
72 """
73
73
74 if not has_superadmin_permission(apiuser):
74 if not has_superadmin_permission(apiuser):
75 # make sure normal user does not pass someone else userid,
75 # make sure normal user does not pass someone else userid,
76 # he is not allowed to do that
76 # he is not allowed to do that
77 if not isinstance(userid, Optional) and userid != apiuser.user_id:
77 if not isinstance(userid, Optional) and userid != apiuser.user_id:
78 raise JSONRPCError(
78 raise JSONRPCError(
79 'userid is not the same as your user'
79 'userid is not the same as your user'
80 )
80 )
81
81
82 if isinstance(userid, Optional):
82 if isinstance(userid, Optional):
83 user_id = apiuser.user_id
83 user_id = apiuser.user_id
84 else:
84 else:
85 user_id = get_user_or_error(userid).user_id
85 user_id = get_user_or_error(userid).user_id
86
86
87 gists = []
87 gists = []
88 _gists = Gist().query() \
88 _gists = Gist().query() \
89 .filter(or_(
89 .filter(or_(
90 Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \
90 Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \
91 .filter(Gist.gist_owner == user_id) \
91 .filter(Gist.gist_owner == user_id) \
92 .order_by(Gist.created_on.desc())
92 .order_by(Gist.created_on.desc())
93 for gist in _gists:
93 for gist in _gists:
94 gists.append(gist.get_api_data())
94 gists.append(gist.get_api_data())
95 return gists
95 return gists
96
96
97
97
98 @jsonrpc_method()
98 @jsonrpc_method()
99 def create_gist(
99 def create_gist(
100 request, apiuser, files, gistid=Optional(None),
100 request, apiuser, files, gistid=Optional(None),
101 owner=Optional(OAttr('apiuser')),
101 owner=Optional(OAttr('apiuser')),
102 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
102 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
103 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
103 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
104 description=Optional('')):
104 description=Optional('')):
105 """
105 """
106 Creates a new Gist.
106 Creates a new Gist.
107
107
108 :param apiuser: This is filled automatically from the |authtoken|.
108 :param apiuser: This is filled automatically from the |authtoken|.
109 :type apiuser: AuthUser
109 :type apiuser: AuthUser
110 :param files: files to be added to the gist. The data structure has
110 :param files: files to be added to the gist. The data structure has
111 to match the following example::
111 to match the following example::
112
112
113 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
113 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
114
114
115 :type files: dict
115 :type files: dict
116 :param gistid: Set a custom id for the gist
116 :param gistid: Set a custom id for the gist
117 :type gistid: Optional(str)
117 :type gistid: Optional(str)
118 :param owner: Set the gist owner, defaults to api method caller
118 :param owner: Set the gist owner, defaults to api method caller
119 :type owner: Optional(str or int)
119 :type owner: Optional(str or int)
120 :param gist_type: type of gist ``public`` or ``private``
120 :param gist_type: type of gist ``public`` or ``private``
121 :type gist_type: Optional(str)
121 :type gist_type: Optional(str)
122 :param lifetime: time in minutes of gist lifetime
122 :param lifetime: time in minutes of gist lifetime
123 :type lifetime: Optional(int)
123 :type lifetime: Optional(int)
124 :param acl_level: acl level for this gist, can be
124 :param acl_level: acl level for this gist, can be
125 ``acl_public`` or ``acl_private`` If the value is set to
125 ``acl_public`` or ``acl_private`` If the value is set to
126 ``acl_private`` only logged in users are able to access this gist.
126 ``acl_private`` only logged in users are able to access this gist.
127 If not set it defaults to ``acl_public``.
127 If not set it defaults to ``acl_public``.
128 :type acl_level: Optional(str)
128 :type acl_level: Optional(str)
129 :param description: gist description
129 :param description: gist description
130 :type description: Optional(str)
130 :type description: Optional(str)
131
131
132 Example output:
132 Example output:
133
133
134 .. code-block:: bash
134 .. code-block:: bash
135
135
136 id : <id_given_in_input>
136 id : <id_given_in_input>
137 result : {
137 result : {
138 "msg": "created new gist",
138 "msg": "created new gist",
139 "gist": {}
139 "gist": {}
140 }
140 }
141 error : null
141 error : null
142
142
143 Example error output:
143 Example error output:
144
144
145 .. code-block:: bash
145 .. code-block:: bash
146
146
147 id : <id_given_in_input>
147 id : <id_given_in_input>
148 result : null
148 result : null
149 error : {
149 error : {
150 "failed to create gist"
150 "failed to create gist"
151 }
151 }
152
152
153 """
153 """
154 from rhodecode.model import validation_schema
154 from rhodecode.model import validation_schema
155 from rhodecode.model.validation_schema.schemas import gist_schema
155 from rhodecode.model.validation_schema.schemas import gist_schema
156
156
157 if isinstance(owner, Optional):
157 if isinstance(owner, Optional):
158 owner = apiuser.user_id
158 owner = apiuser.user_id
159
159
160 owner = get_user_or_error(owner)
160 owner = get_user_or_error(owner)
161
161
162 lifetime = Optional.extract(lifetime)
162 lifetime = Optional.extract(lifetime)
163 schema = gist_schema.GistSchema().bind(
163 schema = gist_schema.GistSchema().bind(
164 # bind the given values if it's allowed, however the deferred
164 # bind the given values if it's allowed, however the deferred
165 # validator will still validate it according to other rules
165 # validator will still validate it according to other rules
166 lifetime_options=[lifetime])
166 lifetime_options=[lifetime])
167
167
168 try:
168 try:
169 nodes = gist_schema.nodes_to_sequence(
169 nodes = gist_schema.nodes_to_sequence(
170 files, colander_node=schema.get('nodes'))
170 files, colander_node=schema.get('nodes'))
171
171
172 schema_data = schema.deserialize(dict(
172 schema_data = schema.deserialize(dict(
173 gistid=Optional.extract(gistid),
173 gistid=Optional.extract(gistid),
174 description=Optional.extract(description),
174 description=Optional.extract(description),
175 gist_type=Optional.extract(gist_type),
175 gist_type=Optional.extract(gist_type),
176 lifetime=lifetime,
176 lifetime=lifetime,
177 gist_acl_level=Optional.extract(acl_level),
177 gist_acl_level=Optional.extract(acl_level),
178 nodes=nodes
178 nodes=nodes
179 ))
179 ))
180
180
181 # convert to safer format with just KEYs so we sure no duplicates
181 # convert to safer format with just KEYs so we sure no duplicates
182 schema_data['nodes'] = gist_schema.sequence_to_nodes(
182 schema_data['nodes'] = gist_schema.sequence_to_nodes(
183 schema_data['nodes'], colander_node=schema.get('nodes'))
183 schema_data['nodes'], colander_node=schema.get('nodes'))
184
184
185 except validation_schema.Invalid as err:
185 except validation_schema.Invalid as err:
186 raise JSONRPCValidationError(colander_exc=err)
186 raise JSONRPCValidationError(colander_exc=err)
187
187
188 try:
188 try:
189 gist = GistModel().create(
189 gist = GistModel().create(
190 owner=owner,
190 owner=owner,
191 gist_id=schema_data['gistid'],
191 gist_id=schema_data['gistid'],
192 description=schema_data['description'],
192 description=schema_data['description'],
193 gist_mapping=schema_data['nodes'],
193 gist_mapping=schema_data['nodes'],
194 gist_type=schema_data['gist_type'],
194 gist_type=schema_data['gist_type'],
195 lifetime=schema_data['lifetime'],
195 lifetime=schema_data['lifetime'],
196 gist_acl_level=schema_data['gist_acl_level'])
196 gist_acl_level=schema_data['gist_acl_level'])
197 Session().commit()
197 Session().commit()
198 return {
198 return {
199 'msg': 'created new gist',
199 'msg': 'created new gist',
200 'gist': gist.get_api_data()
200 'gist': gist.get_api_data()
201 }
201 }
202 except Exception:
202 except Exception:
203 log.exception('Error occurred during creation of gist')
203 log.exception('Error occurred during creation of gist')
204 raise JSONRPCError('failed to create gist')
204 raise JSONRPCError('failed to create gist')
205
205
206
206
207 @jsonrpc_method()
207 @jsonrpc_method()
208 def delete_gist(request, apiuser, gistid):
208 def delete_gist(request, apiuser, gistid):
209 """
209 """
210 Deletes existing gist
210 Deletes existing gist
211
211
212 :param apiuser: filled automatically from apikey
212 :param apiuser: filled automatically from apikey
213 :type apiuser: AuthUser
213 :type apiuser: AuthUser
214 :param gistid: id of gist to delete
214 :param gistid: id of gist to delete
215 :type gistid: str
215 :type gistid: str
216
216
217 Example output:
217 Example output:
218
218
219 .. code-block:: bash
219 .. code-block:: bash
220
220
221 id : <id_given_in_input>
221 id : <id_given_in_input>
222 result : {
222 result : {
223 "deleted gist ID: <gist_id>",
223 "deleted gist ID: <gist_id>",
224 "gist": null
224 "gist": null
225 }
225 }
226 error : null
226 error : null
227
227
228 Example error output:
228 Example error output:
229
229
230 .. code-block:: bash
230 .. code-block:: bash
231
231
232 id : <id_given_in_input>
232 id : <id_given_in_input>
233 result : null
233 result : null
234 error : {
234 error : {
235 "failed to delete gist ID:<gist_id>"
235 "failed to delete gist ID:<gist_id>"
236 }
236 }
237
237
238 """
238 """
239
239
240 gist = get_gist_or_error(gistid)
240 gist = get_gist_or_error(gistid)
241 if not has_superadmin_permission(apiuser):
241 if not has_superadmin_permission(apiuser):
242 if gist.gist_owner != apiuser.user_id:
242 if gist.gist_owner != apiuser.user_id:
243 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
243 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
244
244
245 try:
245 try:
246 GistModel().delete(gist)
246 GistModel().delete(gist)
247 Session().commit()
247 Session().commit()
248 return {
248 return {
249 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,),
249 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,),
250 'gist': None
250 'gist': None
251 }
251 }
252 except Exception:
252 except Exception:
253 log.exception('Error occured during gist deletion')
253 log.exception('Error occured during gist deletion')
254 raise JSONRPCError('failed to delete gist ID:%s'
254 raise JSONRPCError('failed to delete gist ID:%s'
255 % (gist.gist_access_id,)) No newline at end of file
255 % (gist.gist_access_id,))
@@ -1,903 +1,903 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode import events
24 from rhodecode import events
25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
29 validate_repo_permissions, resolve_ref_or_error)
29 validate_repo_permissions, resolve_ref_or_error)
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
31 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.base import vcs_operation_context
32 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.model.changeset_status import ChangesetStatusModel
33 from rhodecode.model.changeset_status import ChangesetStatusModel
34 from rhodecode.model.comment import CommentsModel
34 from rhodecode.model.comment import CommentsModel
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 from rhodecode.model.settings import SettingsModel
37 from rhodecode.model.settings import SettingsModel
38 from rhodecode.model.validation_schema import Invalid
38 from rhodecode.model.validation_schema import Invalid
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
40 ReviewerListSchema)
40 ReviewerListSchema)
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 @jsonrpc_method()
45 @jsonrpc_method()
46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None)):
46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None)):
47 """
47 """
48 Get a pull request based on the given ID.
48 Get a pull request based on the given ID.
49
49
50 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
51 :type apiuser: AuthUser
51 :type apiuser: AuthUser
52 :param repoid: Optional, repository name or repository ID from where
52 :param repoid: Optional, repository name or repository ID from where
53 the pull request was opened.
53 the pull request was opened.
54 :type repoid: str or int
54 :type repoid: str or int
55 :param pullrequestid: ID of the requested pull request.
55 :param pullrequestid: ID of the requested pull request.
56 :type pullrequestid: int
56 :type pullrequestid: int
57
57
58 Example output:
58 Example output:
59
59
60 .. code-block:: bash
60 .. code-block:: bash
61
61
62 "id": <id_given_in_input>,
62 "id": <id_given_in_input>,
63 "result":
63 "result":
64 {
64 {
65 "pull_request_id": "<pull_request_id>",
65 "pull_request_id": "<pull_request_id>",
66 "url": "<url>",
66 "url": "<url>",
67 "title": "<title>",
67 "title": "<title>",
68 "description": "<description>",
68 "description": "<description>",
69 "status" : "<status>",
69 "status" : "<status>",
70 "created_on": "<date_time_created>",
70 "created_on": "<date_time_created>",
71 "updated_on": "<date_time_updated>",
71 "updated_on": "<date_time_updated>",
72 "commit_ids": [
72 "commit_ids": [
73 ...
73 ...
74 "<commit_id>",
74 "<commit_id>",
75 "<commit_id>",
75 "<commit_id>",
76 ...
76 ...
77 ],
77 ],
78 "review_status": "<review_status>",
78 "review_status": "<review_status>",
79 "mergeable": {
79 "mergeable": {
80 "status": "<bool>",
80 "status": "<bool>",
81 "message": "<message>",
81 "message": "<message>",
82 },
82 },
83 "source": {
83 "source": {
84 "clone_url": "<clone_url>",
84 "clone_url": "<clone_url>",
85 "repository": "<repository_name>",
85 "repository": "<repository_name>",
86 "reference":
86 "reference":
87 {
87 {
88 "name": "<name>",
88 "name": "<name>",
89 "type": "<type>",
89 "type": "<type>",
90 "commit_id": "<commit_id>",
90 "commit_id": "<commit_id>",
91 }
91 }
92 },
92 },
93 "target": {
93 "target": {
94 "clone_url": "<clone_url>",
94 "clone_url": "<clone_url>",
95 "repository": "<repository_name>",
95 "repository": "<repository_name>",
96 "reference":
96 "reference":
97 {
97 {
98 "name": "<name>",
98 "name": "<name>",
99 "type": "<type>",
99 "type": "<type>",
100 "commit_id": "<commit_id>",
100 "commit_id": "<commit_id>",
101 }
101 }
102 },
102 },
103 "merge": {
103 "merge": {
104 "clone_url": "<clone_url>",
104 "clone_url": "<clone_url>",
105 "reference":
105 "reference":
106 {
106 {
107 "name": "<name>",
107 "name": "<name>",
108 "type": "<type>",
108 "type": "<type>",
109 "commit_id": "<commit_id>",
109 "commit_id": "<commit_id>",
110 }
110 }
111 },
111 },
112 "author": <user_obj>,
112 "author": <user_obj>,
113 "reviewers": [
113 "reviewers": [
114 ...
114 ...
115 {
115 {
116 "user": "<user_obj>",
116 "user": "<user_obj>",
117 "review_status": "<review_status>",
117 "review_status": "<review_status>",
118 }
118 }
119 ...
119 ...
120 ]
120 ]
121 },
121 },
122 "error": null
122 "error": null
123 """
123 """
124
124
125 pull_request = get_pull_request_or_error(pullrequestid)
125 pull_request = get_pull_request_or_error(pullrequestid)
126 if Optional.extract(repoid):
126 if Optional.extract(repoid):
127 repo = get_repo_or_error(repoid)
127 repo = get_repo_or_error(repoid)
128 else:
128 else:
129 repo = pull_request.target_repo
129 repo = pull_request.target_repo
130
130
131 if not PullRequestModel().check_user_read(
131 if not PullRequestModel().check_user_read(
132 pull_request, apiuser, api=True):
132 pull_request, apiuser, api=True):
133 raise JSONRPCError('repository `%s` or pull request `%s` '
133 raise JSONRPCError('repository `%s` or pull request `%s` '
134 'does not exist' % (repoid, pullrequestid))
134 'does not exist' % (repoid, pullrequestid))
135 data = pull_request.get_api_data()
135 data = pull_request.get_api_data()
136 return data
136 return data
137
137
138
138
139 @jsonrpc_method()
139 @jsonrpc_method()
140 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
140 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
141 """
141 """
142 Get all pull requests from the repository specified in `repoid`.
142 Get all pull requests from the repository specified in `repoid`.
143
143
144 :param apiuser: This is filled automatically from the |authtoken|.
144 :param apiuser: This is filled automatically from the |authtoken|.
145 :type apiuser: AuthUser
145 :type apiuser: AuthUser
146 :param repoid: Optional repository name or repository ID.
146 :param repoid: Optional repository name or repository ID.
147 :type repoid: str or int
147 :type repoid: str or int
148 :param status: Only return pull requests with the specified status.
148 :param status: Only return pull requests with the specified status.
149 Valid options are.
149 Valid options are.
150 * ``new`` (default)
150 * ``new`` (default)
151 * ``open``
151 * ``open``
152 * ``closed``
152 * ``closed``
153 :type status: str
153 :type status: str
154
154
155 Example output:
155 Example output:
156
156
157 .. code-block:: bash
157 .. code-block:: bash
158
158
159 "id": <id_given_in_input>,
159 "id": <id_given_in_input>,
160 "result":
160 "result":
161 [
161 [
162 ...
162 ...
163 {
163 {
164 "pull_request_id": "<pull_request_id>",
164 "pull_request_id": "<pull_request_id>",
165 "url": "<url>",
165 "url": "<url>",
166 "title" : "<title>",
166 "title" : "<title>",
167 "description": "<description>",
167 "description": "<description>",
168 "status": "<status>",
168 "status": "<status>",
169 "created_on": "<date_time_created>",
169 "created_on": "<date_time_created>",
170 "updated_on": "<date_time_updated>",
170 "updated_on": "<date_time_updated>",
171 "commit_ids": [
171 "commit_ids": [
172 ...
172 ...
173 "<commit_id>",
173 "<commit_id>",
174 "<commit_id>",
174 "<commit_id>",
175 ...
175 ...
176 ],
176 ],
177 "review_status": "<review_status>",
177 "review_status": "<review_status>",
178 "mergeable": {
178 "mergeable": {
179 "status": "<bool>",
179 "status": "<bool>",
180 "message: "<message>",
180 "message: "<message>",
181 },
181 },
182 "source": {
182 "source": {
183 "clone_url": "<clone_url>",
183 "clone_url": "<clone_url>",
184 "reference":
184 "reference":
185 {
185 {
186 "name": "<name>",
186 "name": "<name>",
187 "type": "<type>",
187 "type": "<type>",
188 "commit_id": "<commit_id>",
188 "commit_id": "<commit_id>",
189 }
189 }
190 },
190 },
191 "target": {
191 "target": {
192 "clone_url": "<clone_url>",
192 "clone_url": "<clone_url>",
193 "reference":
193 "reference":
194 {
194 {
195 "name": "<name>",
195 "name": "<name>",
196 "type": "<type>",
196 "type": "<type>",
197 "commit_id": "<commit_id>",
197 "commit_id": "<commit_id>",
198 }
198 }
199 },
199 },
200 "merge": {
200 "merge": {
201 "clone_url": "<clone_url>",
201 "clone_url": "<clone_url>",
202 "reference":
202 "reference":
203 {
203 {
204 "name": "<name>",
204 "name": "<name>",
205 "type": "<type>",
205 "type": "<type>",
206 "commit_id": "<commit_id>",
206 "commit_id": "<commit_id>",
207 }
207 }
208 },
208 },
209 "author": <user_obj>,
209 "author": <user_obj>,
210 "reviewers": [
210 "reviewers": [
211 ...
211 ...
212 {
212 {
213 "user": "<user_obj>",
213 "user": "<user_obj>",
214 "review_status": "<review_status>",
214 "review_status": "<review_status>",
215 }
215 }
216 ...
216 ...
217 ]
217 ]
218 }
218 }
219 ...
219 ...
220 ],
220 ],
221 "error": null
221 "error": null
222
222
223 """
223 """
224 repo = get_repo_or_error(repoid)
224 repo = get_repo_or_error(repoid)
225 if not has_superadmin_permission(apiuser):
225 if not has_superadmin_permission(apiuser):
226 _perms = (
226 _perms = (
227 'repository.admin', 'repository.write', 'repository.read',)
227 'repository.admin', 'repository.write', 'repository.read',)
228 validate_repo_permissions(apiuser, repoid, repo, _perms)
228 validate_repo_permissions(apiuser, repoid, repo, _perms)
229
229
230 status = Optional.extract(status)
230 status = Optional.extract(status)
231 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
231 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
232 data = [pr.get_api_data() for pr in pull_requests]
232 data = [pr.get_api_data() for pr in pull_requests]
233 return data
233 return data
234
234
235
235
236 @jsonrpc_method()
236 @jsonrpc_method()
237 def merge_pull_request(
237 def merge_pull_request(
238 request, apiuser, pullrequestid, repoid=Optional(None),
238 request, apiuser, pullrequestid, repoid=Optional(None),
239 userid=Optional(OAttr('apiuser'))):
239 userid=Optional(OAttr('apiuser'))):
240 """
240 """
241 Merge the pull request specified by `pullrequestid` into its target
241 Merge the pull request specified by `pullrequestid` into its target
242 repository.
242 repository.
243
243
244 :param apiuser: This is filled automatically from the |authtoken|.
244 :param apiuser: This is filled automatically from the |authtoken|.
245 :type apiuser: AuthUser
245 :type apiuser: AuthUser
246 :param repoid: Optional, repository name or repository ID of the
246 :param repoid: Optional, repository name or repository ID of the
247 target repository to which the |pr| is to be merged.
247 target repository to which the |pr| is to be merged.
248 :type repoid: str or int
248 :type repoid: str or int
249 :param pullrequestid: ID of the pull request which shall be merged.
249 :param pullrequestid: ID of the pull request which shall be merged.
250 :type pullrequestid: int
250 :type pullrequestid: int
251 :param userid: Merge the pull request as this user.
251 :param userid: Merge the pull request as this user.
252 :type userid: Optional(str or int)
252 :type userid: Optional(str or int)
253
253
254 Example output:
254 Example output:
255
255
256 .. code-block:: bash
256 .. code-block:: bash
257
257
258 "id": <id_given_in_input>,
258 "id": <id_given_in_input>,
259 "result": {
259 "result": {
260 "executed": "<bool>",
260 "executed": "<bool>",
261 "failure_reason": "<int>",
261 "failure_reason": "<int>",
262 "merge_commit_id": "<merge_commit_id>",
262 "merge_commit_id": "<merge_commit_id>",
263 "possible": "<bool>",
263 "possible": "<bool>",
264 "merge_ref": {
264 "merge_ref": {
265 "commit_id": "<commit_id>",
265 "commit_id": "<commit_id>",
266 "type": "<type>",
266 "type": "<type>",
267 "name": "<name>"
267 "name": "<name>"
268 }
268 }
269 },
269 },
270 "error": null
270 "error": null
271 """
271 """
272 pull_request = get_pull_request_or_error(pullrequestid)
272 pull_request = get_pull_request_or_error(pullrequestid)
273 if Optional.extract(repoid):
273 if Optional.extract(repoid):
274 repo = get_repo_or_error(repoid)
274 repo = get_repo_or_error(repoid)
275 else:
275 else:
276 repo = pull_request.target_repo
276 repo = pull_request.target_repo
277
277
278 if not isinstance(userid, Optional):
278 if not isinstance(userid, Optional):
279 if (has_superadmin_permission(apiuser) or
279 if (has_superadmin_permission(apiuser) or
280 HasRepoPermissionAnyApi('repository.admin')(
280 HasRepoPermissionAnyApi('repository.admin')(
281 user=apiuser, repo_name=repo.repo_name)):
281 user=apiuser, repo_name=repo.repo_name)):
282 apiuser = get_user_or_error(userid)
282 apiuser = get_user_or_error(userid)
283 else:
283 else:
284 raise JSONRPCError('userid is not the same as your user')
284 raise JSONRPCError('userid is not the same as your user')
285
285
286 check = MergeCheck.validate(
286 check = MergeCheck.validate(
287 pull_request, user=apiuser, translator=request.translate)
287 pull_request, user=apiuser, translator=request.translate)
288 merge_possible = not check.failed
288 merge_possible = not check.failed
289
289
290 if not merge_possible:
290 if not merge_possible:
291 error_messages = []
291 error_messages = []
292 for err_type, error_msg in check.errors:
292 for err_type, error_msg in check.errors:
293 error_msg = request.translate(error_msg)
293 error_msg = request.translate(error_msg)
294 error_messages.append(error_msg)
294 error_messages.append(error_msg)
295
295
296 reasons = ','.join(error_messages)
296 reasons = ','.join(error_messages)
297 raise JSONRPCError(
297 raise JSONRPCError(
298 'merge not possible for following reasons: {}'.format(reasons))
298 'merge not possible for following reasons: {}'.format(reasons))
299
299
300 target_repo = pull_request.target_repo
300 target_repo = pull_request.target_repo
301 extras = vcs_operation_context(
301 extras = vcs_operation_context(
302 request.environ, repo_name=target_repo.repo_name,
302 request.environ, repo_name=target_repo.repo_name,
303 username=apiuser.username, action='push',
303 username=apiuser.username, action='push',
304 scm=target_repo.repo_type)
304 scm=target_repo.repo_type)
305 merge_response = PullRequestModel().merge(
305 merge_response = PullRequestModel().merge(
306 pull_request, apiuser, extras=extras)
306 pull_request, apiuser, extras=extras)
307 if merge_response.executed:
307 if merge_response.executed:
308 PullRequestModel().close_pull_request(
308 PullRequestModel().close_pull_request(
309 pull_request.pull_request_id, apiuser)
309 pull_request.pull_request_id, apiuser)
310
310
311 Session().commit()
311 Session().commit()
312
312
313 # In previous versions the merge response directly contained the merge
313 # In previous versions the merge response directly contained the merge
314 # commit id. It is now contained in the merge reference object. To be
314 # commit id. It is now contained in the merge reference object. To be
315 # backwards compatible we have to extract it again.
315 # backwards compatible we have to extract it again.
316 merge_response = merge_response._asdict()
316 merge_response = merge_response._asdict()
317 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
317 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
318
318
319 return merge_response
319 return merge_response
320
320
321
321
322 @jsonrpc_method()
322 @jsonrpc_method()
323 def get_pull_request_comments(
323 def get_pull_request_comments(
324 request, apiuser, pullrequestid, repoid=Optional(None)):
324 request, apiuser, pullrequestid, repoid=Optional(None)):
325 """
325 """
326 Get all comments of pull request specified with the `pullrequestid`
326 Get all comments of pull request specified with the `pullrequestid`
327
327
328 :param apiuser: This is filled automatically from the |authtoken|.
328 :param apiuser: This is filled automatically from the |authtoken|.
329 :type apiuser: AuthUser
329 :type apiuser: AuthUser
330 :param repoid: Optional repository name or repository ID.
330 :param repoid: Optional repository name or repository ID.
331 :type repoid: str or int
331 :type repoid: str or int
332 :param pullrequestid: The pull request ID.
332 :param pullrequestid: The pull request ID.
333 :type pullrequestid: int
333 :type pullrequestid: int
334
334
335 Example output:
335 Example output:
336
336
337 .. code-block:: bash
337 .. code-block:: bash
338
338
339 id : <id_given_in_input>
339 id : <id_given_in_input>
340 result : [
340 result : [
341 {
341 {
342 "comment_author": {
342 "comment_author": {
343 "active": true,
343 "active": true,
344 "full_name_or_username": "Tom Gore",
344 "full_name_or_username": "Tom Gore",
345 "username": "admin"
345 "username": "admin"
346 },
346 },
347 "comment_created_on": "2017-01-02T18:43:45.533",
347 "comment_created_on": "2017-01-02T18:43:45.533",
348 "comment_f_path": null,
348 "comment_f_path": null,
349 "comment_id": 25,
349 "comment_id": 25,
350 "comment_lineno": null,
350 "comment_lineno": null,
351 "comment_status": {
351 "comment_status": {
352 "status": "under_review",
352 "status": "under_review",
353 "status_lbl": "Under Review"
353 "status_lbl": "Under Review"
354 },
354 },
355 "comment_text": "Example text",
355 "comment_text": "Example text",
356 "comment_type": null,
356 "comment_type": null,
357 "pull_request_version": null
357 "pull_request_version": null
358 }
358 }
359 ],
359 ],
360 error : null
360 error : null
361 """
361 """
362
362
363 pull_request = get_pull_request_or_error(pullrequestid)
363 pull_request = get_pull_request_or_error(pullrequestid)
364 if Optional.extract(repoid):
364 if Optional.extract(repoid):
365 repo = get_repo_or_error(repoid)
365 repo = get_repo_or_error(repoid)
366 else:
366 else:
367 repo = pull_request.target_repo
367 repo = pull_request.target_repo
368
368
369 if not PullRequestModel().check_user_read(
369 if not PullRequestModel().check_user_read(
370 pull_request, apiuser, api=True):
370 pull_request, apiuser, api=True):
371 raise JSONRPCError('repository `%s` or pull request `%s` '
371 raise JSONRPCError('repository `%s` or pull request `%s` '
372 'does not exist' % (repoid, pullrequestid))
372 'does not exist' % (repoid, pullrequestid))
373
373
374 (pull_request_latest,
374 (pull_request_latest,
375 pull_request_at_ver,
375 pull_request_at_ver,
376 pull_request_display_obj,
376 pull_request_display_obj,
377 at_version) = PullRequestModel().get_pr_version(
377 at_version) = PullRequestModel().get_pr_version(
378 pull_request.pull_request_id, version=None)
378 pull_request.pull_request_id, version=None)
379
379
380 versions = pull_request_display_obj.versions()
380 versions = pull_request_display_obj.versions()
381 ver_map = {
381 ver_map = {
382 ver.pull_request_version_id: cnt
382 ver.pull_request_version_id: cnt
383 for cnt, ver in enumerate(versions, 1)
383 for cnt, ver in enumerate(versions, 1)
384 }
384 }
385
385
386 # GENERAL COMMENTS with versions #
386 # GENERAL COMMENTS with versions #
387 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
387 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
388 q = q.order_by(ChangesetComment.comment_id.asc())
388 q = q.order_by(ChangesetComment.comment_id.asc())
389 general_comments = q.all()
389 general_comments = q.all()
390
390
391 # INLINE COMMENTS with versions #
391 # INLINE COMMENTS with versions #
392 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
392 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
393 q = q.order_by(ChangesetComment.comment_id.asc())
393 q = q.order_by(ChangesetComment.comment_id.asc())
394 inline_comments = q.all()
394 inline_comments = q.all()
395
395
396 data = []
396 data = []
397 for comment in inline_comments + general_comments:
397 for comment in inline_comments + general_comments:
398 full_data = comment.get_api_data()
398 full_data = comment.get_api_data()
399 pr_version_id = None
399 pr_version_id = None
400 if comment.pull_request_version_id:
400 if comment.pull_request_version_id:
401 pr_version_id = 'v{}'.format(
401 pr_version_id = 'v{}'.format(
402 ver_map[comment.pull_request_version_id])
402 ver_map[comment.pull_request_version_id])
403
403
404 # sanitize some entries
404 # sanitize some entries
405
405
406 full_data['pull_request_version'] = pr_version_id
406 full_data['pull_request_version'] = pr_version_id
407 full_data['comment_author'] = {
407 full_data['comment_author'] = {
408 'username': full_data['comment_author'].username,
408 'username': full_data['comment_author'].username,
409 'full_name_or_username': full_data['comment_author'].full_name_or_username,
409 'full_name_or_username': full_data['comment_author'].full_name_or_username,
410 'active': full_data['comment_author'].active,
410 'active': full_data['comment_author'].active,
411 }
411 }
412
412
413 if full_data['comment_status']:
413 if full_data['comment_status']:
414 full_data['comment_status'] = {
414 full_data['comment_status'] = {
415 'status': full_data['comment_status'][0].status,
415 'status': full_data['comment_status'][0].status,
416 'status_lbl': full_data['comment_status'][0].status_lbl,
416 'status_lbl': full_data['comment_status'][0].status_lbl,
417 }
417 }
418 else:
418 else:
419 full_data['comment_status'] = {}
419 full_data['comment_status'] = {}
420
420
421 data.append(full_data)
421 data.append(full_data)
422 return data
422 return data
423
423
424
424
425 @jsonrpc_method()
425 @jsonrpc_method()
426 def comment_pull_request(
426 def comment_pull_request(
427 request, apiuser, pullrequestid, repoid=Optional(None),
427 request, apiuser, pullrequestid, repoid=Optional(None),
428 message=Optional(None), commit_id=Optional(None), status=Optional(None),
428 message=Optional(None), commit_id=Optional(None), status=Optional(None),
429 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
429 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
430 resolves_comment_id=Optional(None),
430 resolves_comment_id=Optional(None),
431 userid=Optional(OAttr('apiuser'))):
431 userid=Optional(OAttr('apiuser'))):
432 """
432 """
433 Comment on the pull request specified with the `pullrequestid`,
433 Comment on the pull request specified with the `pullrequestid`,
434 in the |repo| specified by the `repoid`, and optionally change the
434 in the |repo| specified by the `repoid`, and optionally change the
435 review status.
435 review status.
436
436
437 :param apiuser: This is filled automatically from the |authtoken|.
437 :param apiuser: This is filled automatically from the |authtoken|.
438 :type apiuser: AuthUser
438 :type apiuser: AuthUser
439 :param repoid: Optional repository name or repository ID.
439 :param repoid: Optional repository name or repository ID.
440 :type repoid: str or int
440 :type repoid: str or int
441 :param pullrequestid: The pull request ID.
441 :param pullrequestid: The pull request ID.
442 :type pullrequestid: int
442 :type pullrequestid: int
443 :param commit_id: Specify the commit_id for which to set a comment. If
443 :param commit_id: Specify the commit_id for which to set a comment. If
444 given commit_id is different than latest in the PR status
444 given commit_id is different than latest in the PR status
445 change won't be performed.
445 change won't be performed.
446 :type commit_id: str
446 :type commit_id: str
447 :param message: The text content of the comment.
447 :param message: The text content of the comment.
448 :type message: str
448 :type message: str
449 :param status: (**Optional**) Set the approval status of the pull
449 :param status: (**Optional**) Set the approval status of the pull
450 request. One of: 'not_reviewed', 'approved', 'rejected',
450 request. One of: 'not_reviewed', 'approved', 'rejected',
451 'under_review'
451 'under_review'
452 :type status: str
452 :type status: str
453 :param comment_type: Comment type, one of: 'note', 'todo'
453 :param comment_type: Comment type, one of: 'note', 'todo'
454 :type comment_type: Optional(str), default: 'note'
454 :type comment_type: Optional(str), default: 'note'
455 :param userid: Comment on the pull request as this user
455 :param userid: Comment on the pull request as this user
456 :type userid: Optional(str or int)
456 :type userid: Optional(str or int)
457
457
458 Example output:
458 Example output:
459
459
460 .. code-block:: bash
460 .. code-block:: bash
461
461
462 id : <id_given_in_input>
462 id : <id_given_in_input>
463 result : {
463 result : {
464 "pull_request_id": "<Integer>",
464 "pull_request_id": "<Integer>",
465 "comment_id": "<Integer>",
465 "comment_id": "<Integer>",
466 "status": {"given": <given_status>,
466 "status": {"given": <given_status>,
467 "was_changed": <bool status_was_actually_changed> },
467 "was_changed": <bool status_was_actually_changed> },
468 },
468 },
469 error : null
469 error : null
470 """
470 """
471 pull_request = get_pull_request_or_error(pullrequestid)
471 pull_request = get_pull_request_or_error(pullrequestid)
472 if Optional.extract(repoid):
472 if Optional.extract(repoid):
473 repo = get_repo_or_error(repoid)
473 repo = get_repo_or_error(repoid)
474 else:
474 else:
475 repo = pull_request.target_repo
475 repo = pull_request.target_repo
476
476
477 if not isinstance(userid, Optional):
477 if not isinstance(userid, Optional):
478 if (has_superadmin_permission(apiuser) or
478 if (has_superadmin_permission(apiuser) or
479 HasRepoPermissionAnyApi('repository.admin')(
479 HasRepoPermissionAnyApi('repository.admin')(
480 user=apiuser, repo_name=repo.repo_name)):
480 user=apiuser, repo_name=repo.repo_name)):
481 apiuser = get_user_or_error(userid)
481 apiuser = get_user_or_error(userid)
482 else:
482 else:
483 raise JSONRPCError('userid is not the same as your user')
483 raise JSONRPCError('userid is not the same as your user')
484
484
485 if not PullRequestModel().check_user_read(
485 if not PullRequestModel().check_user_read(
486 pull_request, apiuser, api=True):
486 pull_request, apiuser, api=True):
487 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
487 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
488 message = Optional.extract(message)
488 message = Optional.extract(message)
489 status = Optional.extract(status)
489 status = Optional.extract(status)
490 commit_id = Optional.extract(commit_id)
490 commit_id = Optional.extract(commit_id)
491 comment_type = Optional.extract(comment_type)
491 comment_type = Optional.extract(comment_type)
492 resolves_comment_id = Optional.extract(resolves_comment_id)
492 resolves_comment_id = Optional.extract(resolves_comment_id)
493
493
494 if not message and not status:
494 if not message and not status:
495 raise JSONRPCError(
495 raise JSONRPCError(
496 'Both message and status parameters are missing. '
496 'Both message and status parameters are missing. '
497 'At least one is required.')
497 'At least one is required.')
498
498
499 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
499 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
500 status is not None):
500 status is not None):
501 raise JSONRPCError('Unknown comment status: `%s`' % status)
501 raise JSONRPCError('Unknown comment status: `%s`' % status)
502
502
503 if commit_id and commit_id not in pull_request.revisions:
503 if commit_id and commit_id not in pull_request.revisions:
504 raise JSONRPCError(
504 raise JSONRPCError(
505 'Invalid commit_id `%s` for this pull request.' % commit_id)
505 'Invalid commit_id `%s` for this pull request.' % commit_id)
506
506
507 allowed_to_change_status = PullRequestModel().check_user_change_status(
507 allowed_to_change_status = PullRequestModel().check_user_change_status(
508 pull_request, apiuser)
508 pull_request, apiuser)
509
509
510 # if commit_id is passed re-validated if user is allowed to change status
510 # if commit_id is passed re-validated if user is allowed to change status
511 # based on latest commit_id from the PR
511 # based on latest commit_id from the PR
512 if commit_id:
512 if commit_id:
513 commit_idx = pull_request.revisions.index(commit_id)
513 commit_idx = pull_request.revisions.index(commit_id)
514 if commit_idx != 0:
514 if commit_idx != 0:
515 allowed_to_change_status = False
515 allowed_to_change_status = False
516
516
517 if resolves_comment_id:
517 if resolves_comment_id:
518 comment = ChangesetComment.get(resolves_comment_id)
518 comment = ChangesetComment.get(resolves_comment_id)
519 if not comment:
519 if not comment:
520 raise JSONRPCError(
520 raise JSONRPCError(
521 'Invalid resolves_comment_id `%s` for this pull request.'
521 'Invalid resolves_comment_id `%s` for this pull request.'
522 % resolves_comment_id)
522 % resolves_comment_id)
523 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
523 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
524 raise JSONRPCError(
524 raise JSONRPCError(
525 'Comment `%s` is wrong type for setting status to resolved.'
525 'Comment `%s` is wrong type for setting status to resolved.'
526 % resolves_comment_id)
526 % resolves_comment_id)
527
527
528 text = message
528 text = message
529 status_label = ChangesetStatus.get_status_lbl(status)
529 status_label = ChangesetStatus.get_status_lbl(status)
530 if status and allowed_to_change_status:
530 if status and allowed_to_change_status:
531 st_message = ('Status change %(transition_icon)s %(status)s'
531 st_message = ('Status change %(transition_icon)s %(status)s'
532 % {'transition_icon': '>', 'status': status_label})
532 % {'transition_icon': '>', 'status': status_label})
533 text = message or st_message
533 text = message or st_message
534
534
535 rc_config = SettingsModel().get_all_settings()
535 rc_config = SettingsModel().get_all_settings()
536 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
536 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
537
537
538 status_change = status and allowed_to_change_status
538 status_change = status and allowed_to_change_status
539 comment = CommentsModel().create(
539 comment = CommentsModel().create(
540 text=text,
540 text=text,
541 repo=pull_request.target_repo.repo_id,
541 repo=pull_request.target_repo.repo_id,
542 user=apiuser.user_id,
542 user=apiuser.user_id,
543 pull_request=pull_request.pull_request_id,
543 pull_request=pull_request.pull_request_id,
544 f_path=None,
544 f_path=None,
545 line_no=None,
545 line_no=None,
546 status_change=(status_label if status_change else None),
546 status_change=(status_label if status_change else None),
547 status_change_type=(status if status_change else None),
547 status_change_type=(status if status_change else None),
548 closing_pr=False,
548 closing_pr=False,
549 renderer=renderer,
549 renderer=renderer,
550 comment_type=comment_type,
550 comment_type=comment_type,
551 resolves_comment_id=resolves_comment_id
551 resolves_comment_id=resolves_comment_id
552 )
552 )
553
553
554 if allowed_to_change_status and status:
554 if allowed_to_change_status and status:
555 ChangesetStatusModel().set_status(
555 ChangesetStatusModel().set_status(
556 pull_request.target_repo.repo_id,
556 pull_request.target_repo.repo_id,
557 status,
557 status,
558 apiuser.user_id,
558 apiuser.user_id,
559 comment,
559 comment,
560 pull_request=pull_request.pull_request_id
560 pull_request=pull_request.pull_request_id
561 )
561 )
562 Session().flush()
562 Session().flush()
563
563
564 Session().commit()
564 Session().commit()
565 data = {
565 data = {
566 'pull_request_id': pull_request.pull_request_id,
566 'pull_request_id': pull_request.pull_request_id,
567 'comment_id': comment.comment_id if comment else None,
567 'comment_id': comment.comment_id if comment else None,
568 'status': {'given': status, 'was_changed': status_change},
568 'status': {'given': status, 'was_changed': status_change},
569 }
569 }
570 return data
570 return data
571
571
572
572
573 @jsonrpc_method()
573 @jsonrpc_method()
574 def create_pull_request(
574 def create_pull_request(
575 request, apiuser, source_repo, target_repo, source_ref, target_ref,
575 request, apiuser, source_repo, target_repo, source_ref, target_ref,
576 title, description=Optional(''), reviewers=Optional(None)):
576 title, description=Optional(''), reviewers=Optional(None)):
577 """
577 """
578 Creates a new pull request.
578 Creates a new pull request.
579
579
580 Accepts refs in the following formats:
580 Accepts refs in the following formats:
581
581
582 * branch:<branch_name>:<sha>
582 * branch:<branch_name>:<sha>
583 * branch:<branch_name>
583 * branch:<branch_name>
584 * bookmark:<bookmark_name>:<sha> (Mercurial only)
584 * bookmark:<bookmark_name>:<sha> (Mercurial only)
585 * bookmark:<bookmark_name> (Mercurial only)
585 * bookmark:<bookmark_name> (Mercurial only)
586
586
587 :param apiuser: This is filled automatically from the |authtoken|.
587 :param apiuser: This is filled automatically from the |authtoken|.
588 :type apiuser: AuthUser
588 :type apiuser: AuthUser
589 :param source_repo: Set the source repository name.
589 :param source_repo: Set the source repository name.
590 :type source_repo: str
590 :type source_repo: str
591 :param target_repo: Set the target repository name.
591 :param target_repo: Set the target repository name.
592 :type target_repo: str
592 :type target_repo: str
593 :param source_ref: Set the source ref name.
593 :param source_ref: Set the source ref name.
594 :type source_ref: str
594 :type source_ref: str
595 :param target_ref: Set the target ref name.
595 :param target_ref: Set the target ref name.
596 :type target_ref: str
596 :type target_ref: str
597 :param title: Set the pull request title.
597 :param title: Set the pull request title.
598 :type title: str
598 :type title: str
599 :param description: Set the pull request description.
599 :param description: Set the pull request description.
600 :type description: Optional(str)
600 :type description: Optional(str)
601 :param reviewers: Set the new pull request reviewers list.
601 :param reviewers: Set the new pull request reviewers list.
602 Reviewer defined by review rules will be added automatically to the
602 Reviewer defined by review rules will be added automatically to the
603 defined list.
603 defined list.
604 :type reviewers: Optional(list)
604 :type reviewers: Optional(list)
605 Accepts username strings or objects of the format:
605 Accepts username strings or objects of the format:
606
606
607 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
607 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
608 """
608 """
609
609
610 source_db_repo = get_repo_or_error(source_repo)
610 source_db_repo = get_repo_or_error(source_repo)
611 target_db_repo = get_repo_or_error(target_repo)
611 target_db_repo = get_repo_or_error(target_repo)
612 if not has_superadmin_permission(apiuser):
612 if not has_superadmin_permission(apiuser):
613 _perms = ('repository.admin', 'repository.write', 'repository.read',)
613 _perms = ('repository.admin', 'repository.write', 'repository.read',)
614 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
614 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
615
615
616 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
616 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
617 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
617 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
618 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
618 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
619 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
619 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
620 source_scm = source_db_repo.scm_instance()
620 source_scm = source_db_repo.scm_instance()
621 target_scm = target_db_repo.scm_instance()
621 target_scm = target_db_repo.scm_instance()
622
622
623 commit_ranges = target_scm.compare(
623 commit_ranges = target_scm.compare(
624 target_commit.raw_id, source_commit.raw_id, source_scm,
624 target_commit.raw_id, source_commit.raw_id, source_scm,
625 merge=True, pre_load=[])
625 merge=True, pre_load=[])
626
626
627 ancestor = target_scm.get_common_ancestor(
627 ancestor = target_scm.get_common_ancestor(
628 target_commit.raw_id, source_commit.raw_id, source_scm)
628 target_commit.raw_id, source_commit.raw_id, source_scm)
629
629
630 if not commit_ranges:
630 if not commit_ranges:
631 raise JSONRPCError('no commits found')
631 raise JSONRPCError('no commits found')
632
632
633 if not ancestor:
633 if not ancestor:
634 raise JSONRPCError('no common ancestor found')
634 raise JSONRPCError('no common ancestor found')
635
635
636 reviewer_objects = Optional.extract(reviewers) or []
636 reviewer_objects = Optional.extract(reviewers) or []
637
637
638 if reviewer_objects:
638 if reviewer_objects:
639 schema = ReviewerListSchema()
639 schema = ReviewerListSchema()
640 try:
640 try:
641 reviewer_objects = schema.deserialize(reviewer_objects)
641 reviewer_objects = schema.deserialize(reviewer_objects)
642 except Invalid as err:
642 except Invalid as err:
643 raise JSONRPCValidationError(colander_exc=err)
643 raise JSONRPCValidationError(colander_exc=err)
644
644
645 # validate users
645 # validate users
646 for reviewer_object in reviewer_objects:
646 for reviewer_object in reviewer_objects:
647 user = get_user_or_error(reviewer_object['username'])
647 user = get_user_or_error(reviewer_object['username'])
648 reviewer_object['user_id'] = user.user_id
648 reviewer_object['user_id'] = user.user_id
649
649
650 get_default_reviewers_data, get_validated_reviewers = \
650 get_default_reviewers_data, get_validated_reviewers = \
651 PullRequestModel().get_reviewer_functions()
651 PullRequestModel().get_reviewer_functions()
652
652
653 reviewer_rules = get_default_reviewers_data(
653 reviewer_rules = get_default_reviewers_data(
654 apiuser.get_instance(), source_db_repo,
654 apiuser.get_instance(), source_db_repo,
655 source_commit, target_db_repo, target_commit)
655 source_commit, target_db_repo, target_commit)
656
656
657 # specified rules are later re-validated, thus we can assume users will
657 # specified rules are later re-validated, thus we can assume users will
658 # eventually provide those that meet the reviewer criteria.
658 # eventually provide those that meet the reviewer criteria.
659 if not reviewer_objects:
659 if not reviewer_objects:
660 reviewer_objects = reviewer_rules['reviewers']
660 reviewer_objects = reviewer_rules['reviewers']
661
661
662 try:
662 try:
663 reviewers = get_validated_reviewers(
663 reviewers = get_validated_reviewers(
664 reviewer_objects, reviewer_rules)
664 reviewer_objects, reviewer_rules)
665 except ValueError as e:
665 except ValueError as e:
666 raise JSONRPCError('Reviewers Validation: {}'.format(e))
666 raise JSONRPCError('Reviewers Validation: {}'.format(e))
667
667
668 pull_request_model = PullRequestModel()
668 pull_request_model = PullRequestModel()
669 pull_request = pull_request_model.create(
669 pull_request = pull_request_model.create(
670 created_by=apiuser.user_id,
670 created_by=apiuser.user_id,
671 source_repo=source_repo,
671 source_repo=source_repo,
672 source_ref=full_source_ref,
672 source_ref=full_source_ref,
673 target_repo=target_repo,
673 target_repo=target_repo,
674 target_ref=full_target_ref,
674 target_ref=full_target_ref,
675 revisions=reversed(
675 revisions=reversed(
676 [commit.raw_id for commit in reversed(commit_ranges)]),
676 [commit.raw_id for commit in reversed(commit_ranges)]),
677 reviewers=reviewers,
677 reviewers=reviewers,
678 title=title,
678 title=title,
679 description=Optional.extract(description)
679 description=Optional.extract(description)
680 )
680 )
681
681
682 Session().commit()
682 Session().commit()
683 data = {
683 data = {
684 'msg': 'Created new pull request `{}`'.format(title),
684 'msg': 'Created new pull request `{}`'.format(title),
685 'pull_request_id': pull_request.pull_request_id,
685 'pull_request_id': pull_request.pull_request_id,
686 }
686 }
687 return data
687 return data
688
688
689
689
690 @jsonrpc_method()
690 @jsonrpc_method()
691 def update_pull_request(
691 def update_pull_request(
692 request, apiuser, pullrequestid, repoid=Optional(None),
692 request, apiuser, pullrequestid, repoid=Optional(None),
693 title=Optional(''), description=Optional(''), reviewers=Optional(None),
693 title=Optional(''), description=Optional(''), reviewers=Optional(None),
694 update_commits=Optional(None)):
694 update_commits=Optional(None)):
695 """
695 """
696 Updates a pull request.
696 Updates a pull request.
697
697
698 :param apiuser: This is filled automatically from the |authtoken|.
698 :param apiuser: This is filled automatically from the |authtoken|.
699 :type apiuser: AuthUser
699 :type apiuser: AuthUser
700 :param repoid: Optional repository name or repository ID.
700 :param repoid: Optional repository name or repository ID.
701 :type repoid: str or int
701 :type repoid: str or int
702 :param pullrequestid: The pull request ID.
702 :param pullrequestid: The pull request ID.
703 :type pullrequestid: int
703 :type pullrequestid: int
704 :param title: Set the pull request title.
704 :param title: Set the pull request title.
705 :type title: str
705 :type title: str
706 :param description: Update pull request description.
706 :param description: Update pull request description.
707 :type description: Optional(str)
707 :type description: Optional(str)
708 :param reviewers: Update pull request reviewers list with new value.
708 :param reviewers: Update pull request reviewers list with new value.
709 :type reviewers: Optional(list)
709 :type reviewers: Optional(list)
710 Accepts username strings or objects of the format:
710 Accepts username strings or objects of the format:
711
711
712 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
712 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
713
713
714 :param update_commits: Trigger update of commits for this pull request
714 :param update_commits: Trigger update of commits for this pull request
715 :type: update_commits: Optional(bool)
715 :type: update_commits: Optional(bool)
716
716
717 Example output:
717 Example output:
718
718
719 .. code-block:: bash
719 .. code-block:: bash
720
720
721 id : <id_given_in_input>
721 id : <id_given_in_input>
722 result : {
722 result : {
723 "msg": "Updated pull request `63`",
723 "msg": "Updated pull request `63`",
724 "pull_request": <pull_request_object>,
724 "pull_request": <pull_request_object>,
725 "updated_reviewers": {
725 "updated_reviewers": {
726 "added": [
726 "added": [
727 "username"
727 "username"
728 ],
728 ],
729 "removed": []
729 "removed": []
730 },
730 },
731 "updated_commits": {
731 "updated_commits": {
732 "added": [
732 "added": [
733 "<sha1_hash>"
733 "<sha1_hash>"
734 ],
734 ],
735 "common": [
735 "common": [
736 "<sha1_hash>",
736 "<sha1_hash>",
737 "<sha1_hash>",
737 "<sha1_hash>",
738 ],
738 ],
739 "removed": []
739 "removed": []
740 }
740 }
741 }
741 }
742 error : null
742 error : null
743 """
743 """
744
744
745 pull_request = get_pull_request_or_error(pullrequestid)
745 pull_request = get_pull_request_or_error(pullrequestid)
746 if Optional.extract(repoid):
746 if Optional.extract(repoid):
747 repo = get_repo_or_error(repoid)
747 repo = get_repo_or_error(repoid)
748 else:
748 else:
749 repo = pull_request.target_repo
749 repo = pull_request.target_repo
750
750
751 if not PullRequestModel().check_user_update(
751 if not PullRequestModel().check_user_update(
752 pull_request, apiuser, api=True):
752 pull_request, apiuser, api=True):
753 raise JSONRPCError(
753 raise JSONRPCError(
754 'pull request `%s` update failed, no permission to update.' % (
754 'pull request `%s` update failed, no permission to update.' % (
755 pullrequestid,))
755 pullrequestid,))
756 if pull_request.is_closed():
756 if pull_request.is_closed():
757 raise JSONRPCError(
757 raise JSONRPCError(
758 'pull request `%s` update failed, pull request is closed' % (
758 'pull request `%s` update failed, pull request is closed' % (
759 pullrequestid,))
759 pullrequestid,))
760
760
761 reviewer_objects = Optional.extract(reviewers) or []
761 reviewer_objects = Optional.extract(reviewers) or []
762
762
763 if reviewer_objects:
763 if reviewer_objects:
764 schema = ReviewerListSchema()
764 schema = ReviewerListSchema()
765 try:
765 try:
766 reviewer_objects = schema.deserialize(reviewer_objects)
766 reviewer_objects = schema.deserialize(reviewer_objects)
767 except Invalid as err:
767 except Invalid as err:
768 raise JSONRPCValidationError(colander_exc=err)
768 raise JSONRPCValidationError(colander_exc=err)
769
769
770 # validate users
770 # validate users
771 for reviewer_object in reviewer_objects:
771 for reviewer_object in reviewer_objects:
772 user = get_user_or_error(reviewer_object['username'])
772 user = get_user_or_error(reviewer_object['username'])
773 reviewer_object['user_id'] = user.user_id
773 reviewer_object['user_id'] = user.user_id
774
774
775 get_default_reviewers_data, get_validated_reviewers = \
775 get_default_reviewers_data, get_validated_reviewers = \
776 PullRequestModel().get_reviewer_functions()
776 PullRequestModel().get_reviewer_functions()
777
777
778 # re-use stored rules
778 # re-use stored rules
779 reviewer_rules = pull_request.reviewer_data
779 reviewer_rules = pull_request.reviewer_data
780 try:
780 try:
781 reviewers = get_validated_reviewers(
781 reviewers = get_validated_reviewers(
782 reviewer_objects, reviewer_rules)
782 reviewer_objects, reviewer_rules)
783 except ValueError as e:
783 except ValueError as e:
784 raise JSONRPCError('Reviewers Validation: {}'.format(e))
784 raise JSONRPCError('Reviewers Validation: {}'.format(e))
785 else:
785 else:
786 reviewers = []
786 reviewers = []
787
787
788 title = Optional.extract(title)
788 title = Optional.extract(title)
789 description = Optional.extract(description)
789 description = Optional.extract(description)
790 if title or description:
790 if title or description:
791 PullRequestModel().edit(
791 PullRequestModel().edit(
792 pull_request, title or pull_request.title,
792 pull_request, title or pull_request.title,
793 description or pull_request.description, apiuser)
793 description or pull_request.description, apiuser)
794 Session().commit()
794 Session().commit()
795
795
796 commit_changes = {"added": [], "common": [], "removed": []}
796 commit_changes = {"added": [], "common": [], "removed": []}
797 if str2bool(Optional.extract(update_commits)):
797 if str2bool(Optional.extract(update_commits)):
798 if PullRequestModel().has_valid_update_type(pull_request):
798 if PullRequestModel().has_valid_update_type(pull_request):
799 update_response = PullRequestModel().update_commits(
799 update_response = PullRequestModel().update_commits(
800 pull_request)
800 pull_request)
801 commit_changes = update_response.changes or commit_changes
801 commit_changes = update_response.changes or commit_changes
802 Session().commit()
802 Session().commit()
803
803
804 reviewers_changes = {"added": [], "removed": []}
804 reviewers_changes = {"added": [], "removed": []}
805 if reviewers:
805 if reviewers:
806 added_reviewers, removed_reviewers = \
806 added_reviewers, removed_reviewers = \
807 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
807 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
808
808
809 reviewers_changes['added'] = sorted(
809 reviewers_changes['added'] = sorted(
810 [get_user_or_error(n).username for n in added_reviewers])
810 [get_user_or_error(n).username for n in added_reviewers])
811 reviewers_changes['removed'] = sorted(
811 reviewers_changes['removed'] = sorted(
812 [get_user_or_error(n).username for n in removed_reviewers])
812 [get_user_or_error(n).username for n in removed_reviewers])
813 Session().commit()
813 Session().commit()
814
814
815 data = {
815 data = {
816 'msg': 'Updated pull request `{}`'.format(
816 'msg': 'Updated pull request `{}`'.format(
817 pull_request.pull_request_id),
817 pull_request.pull_request_id),
818 'pull_request': pull_request.get_api_data(),
818 'pull_request': pull_request.get_api_data(),
819 'updated_commits': commit_changes,
819 'updated_commits': commit_changes,
820 'updated_reviewers': reviewers_changes
820 'updated_reviewers': reviewers_changes
821 }
821 }
822
822
823 return data
823 return data
824
824
825
825
826 @jsonrpc_method()
826 @jsonrpc_method()
827 def close_pull_request(
827 def close_pull_request(
828 request, apiuser, pullrequestid, repoid=Optional(None),
828 request, apiuser, pullrequestid, repoid=Optional(None),
829 userid=Optional(OAttr('apiuser')), message=Optional('')):
829 userid=Optional(OAttr('apiuser')), message=Optional('')):
830 """
830 """
831 Close the pull request specified by `pullrequestid`.
831 Close the pull request specified by `pullrequestid`.
832
832
833 :param apiuser: This is filled automatically from the |authtoken|.
833 :param apiuser: This is filled automatically from the |authtoken|.
834 :type apiuser: AuthUser
834 :type apiuser: AuthUser
835 :param repoid: Repository name or repository ID to which the pull
835 :param repoid: Repository name or repository ID to which the pull
836 request belongs.
836 request belongs.
837 :type repoid: str or int
837 :type repoid: str or int
838 :param pullrequestid: ID of the pull request to be closed.
838 :param pullrequestid: ID of the pull request to be closed.
839 :type pullrequestid: int
839 :type pullrequestid: int
840 :param userid: Close the pull request as this user.
840 :param userid: Close the pull request as this user.
841 :type userid: Optional(str or int)
841 :type userid: Optional(str or int)
842 :param message: Optional message to close the Pull Request with. If not
842 :param message: Optional message to close the Pull Request with. If not
843 specified it will be generated automatically.
843 specified it will be generated automatically.
844 :type message: Optional(str)
844 :type message: Optional(str)
845
845
846 Example output:
846 Example output:
847
847
848 .. code-block:: bash
848 .. code-block:: bash
849
849
850 "id": <id_given_in_input>,
850 "id": <id_given_in_input>,
851 "result": {
851 "result": {
852 "pull_request_id": "<int>",
852 "pull_request_id": "<int>",
853 "close_status": "<str:status_lbl>,
853 "close_status": "<str:status_lbl>,
854 "closed": "<bool>"
854 "closed": "<bool>"
855 },
855 },
856 "error": null
856 "error": null
857
857
858 """
858 """
859 _ = request.translate
859 _ = request.translate
860
860
861 pull_request = get_pull_request_or_error(pullrequestid)
861 pull_request = get_pull_request_or_error(pullrequestid)
862 if Optional.extract(repoid):
862 if Optional.extract(repoid):
863 repo = get_repo_or_error(repoid)
863 repo = get_repo_or_error(repoid)
864 else:
864 else:
865 repo = pull_request.target_repo
865 repo = pull_request.target_repo
866
866
867 if not isinstance(userid, Optional):
867 if not isinstance(userid, Optional):
868 if (has_superadmin_permission(apiuser) or
868 if (has_superadmin_permission(apiuser) or
869 HasRepoPermissionAnyApi('repository.admin')(
869 HasRepoPermissionAnyApi('repository.admin')(
870 user=apiuser, repo_name=repo.repo_name)):
870 user=apiuser, repo_name=repo.repo_name)):
871 apiuser = get_user_or_error(userid)
871 apiuser = get_user_or_error(userid)
872 else:
872 else:
873 raise JSONRPCError('userid is not the same as your user')
873 raise JSONRPCError('userid is not the same as your user')
874
874
875 if pull_request.is_closed():
875 if pull_request.is_closed():
876 raise JSONRPCError(
876 raise JSONRPCError(
877 'pull request `%s` is already closed' % (pullrequestid,))
877 'pull request `%s` is already closed' % (pullrequestid,))
878
878
879 # only owner or admin or person with write permissions
879 # only owner or admin or person with write permissions
880 allowed_to_close = PullRequestModel().check_user_update(
880 allowed_to_close = PullRequestModel().check_user_update(
881 pull_request, apiuser, api=True)
881 pull_request, apiuser, api=True)
882
882
883 if not allowed_to_close:
883 if not allowed_to_close:
884 raise JSONRPCError(
884 raise JSONRPCError(
885 'pull request `%s` close failed, no permission to close.' % (
885 'pull request `%s` close failed, no permission to close.' % (
886 pullrequestid,))
886 pullrequestid,))
887
887
888 # message we're using to close the PR, else it's automatically generated
888 # message we're using to close the PR, else it's automatically generated
889 message = Optional.extract(message)
889 message = Optional.extract(message)
890
890
891 # finally close the PR, with proper message comment
891 # finally close the PR, with proper message comment
892 comment, status = PullRequestModel().close_pull_request_with_comment(
892 comment, status = PullRequestModel().close_pull_request_with_comment(
893 pull_request, apiuser, repo, message=message)
893 pull_request, apiuser, repo, message=message)
894 status_lbl = ChangesetStatus.get_status_lbl(status)
894 status_lbl = ChangesetStatus.get_status_lbl(status)
895
895
896 Session().commit()
896 Session().commit()
897
897
898 data = {
898 data = {
899 'pull_request_id': pull_request.pull_request_id,
899 'pull_request_id': pull_request.pull_request_id,
900 'close_status': status_lbl,
900 'close_status': status_lbl,
901 'closed': True,
901 'closed': True,
902 }
902 }
903 return data
903 return data
@@ -1,2042 +1,2042 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import time
22 import time
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.api import (
25 from rhodecode.api import (
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 validate_set_owner_permissions)
31 validate_set_owner_permissions)
32 from rhodecode.lib import audit_logger
32 from rhodecode.lib import audit_logger
33 from rhodecode.lib import repo_maintenance
33 from rhodecode.lib import repo_maintenance
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 from rhodecode.lib.celerylib.utils import get_task_id
35 from rhodecode.lib.celerylib.utils import get_task_id
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime
36 from rhodecode.lib.utils2 import str2bool, time_to_datetime
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 from rhodecode.model.changeset_status import ChangesetStatusModel
39 from rhodecode.model.changeset_status import ChangesetStatusModel
40 from rhodecode.model.comment import CommentsModel
40 from rhodecode.model.comment import CommentsModel
41 from rhodecode.model.db import (
41 from rhodecode.model.db import (
42 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
42 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
43 ChangesetComment)
43 ChangesetComment)
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.scm import ScmModel, RepoList
45 from rhodecode.model.scm import ScmModel, RepoList
46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
47 from rhodecode.model import validation_schema
47 from rhodecode.model import validation_schema
48 from rhodecode.model.validation_schema.schemas import repo_schema
48 from rhodecode.model.validation_schema.schemas import repo_schema
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 @jsonrpc_method()
53 @jsonrpc_method()
54 def get_repo(request, apiuser, repoid, cache=Optional(True)):
54 def get_repo(request, apiuser, repoid, cache=Optional(True)):
55 """
55 """
56 Gets an existing repository by its name or repository_id.
56 Gets an existing repository by its name or repository_id.
57
57
58 The members section so the output returns users groups or users
58 The members section so the output returns users groups or users
59 associated with that repository.
59 associated with that repository.
60
60
61 This command can only be run using an |authtoken| with admin rights,
61 This command can only be run using an |authtoken| with admin rights,
62 or users with at least read rights to the |repo|.
62 or users with at least read rights to the |repo|.
63
63
64 :param apiuser: This is filled automatically from the |authtoken|.
64 :param apiuser: This is filled automatically from the |authtoken|.
65 :type apiuser: AuthUser
65 :type apiuser: AuthUser
66 :param repoid: The repository name or repository id.
66 :param repoid: The repository name or repository id.
67 :type repoid: str or int
67 :type repoid: str or int
68 :param cache: use the cached value for last changeset
68 :param cache: use the cached value for last changeset
69 :type: cache: Optional(bool)
69 :type: cache: Optional(bool)
70
70
71 Example output:
71 Example output:
72
72
73 .. code-block:: bash
73 .. code-block:: bash
74
74
75 {
75 {
76 "error": null,
76 "error": null,
77 "id": <repo_id>,
77 "id": <repo_id>,
78 "result": {
78 "result": {
79 "clone_uri": null,
79 "clone_uri": null,
80 "created_on": "timestamp",
80 "created_on": "timestamp",
81 "description": "repo description",
81 "description": "repo description",
82 "enable_downloads": false,
82 "enable_downloads": false,
83 "enable_locking": false,
83 "enable_locking": false,
84 "enable_statistics": false,
84 "enable_statistics": false,
85 "followers": [
85 "followers": [
86 {
86 {
87 "active": true,
87 "active": true,
88 "admin": false,
88 "admin": false,
89 "api_key": "****************************************",
89 "api_key": "****************************************",
90 "api_keys": [
90 "api_keys": [
91 "****************************************"
91 "****************************************"
92 ],
92 ],
93 "email": "user@example.com",
93 "email": "user@example.com",
94 "emails": [
94 "emails": [
95 "user@example.com"
95 "user@example.com"
96 ],
96 ],
97 "extern_name": "rhodecode",
97 "extern_name": "rhodecode",
98 "extern_type": "rhodecode",
98 "extern_type": "rhodecode",
99 "firstname": "username",
99 "firstname": "username",
100 "ip_addresses": [],
100 "ip_addresses": [],
101 "language": null,
101 "language": null,
102 "last_login": "2015-09-16T17:16:35.854",
102 "last_login": "2015-09-16T17:16:35.854",
103 "lastname": "surname",
103 "lastname": "surname",
104 "user_id": <user_id>,
104 "user_id": <user_id>,
105 "username": "name"
105 "username": "name"
106 }
106 }
107 ],
107 ],
108 "fork_of": "parent-repo",
108 "fork_of": "parent-repo",
109 "landing_rev": [
109 "landing_rev": [
110 "rev",
110 "rev",
111 "tip"
111 "tip"
112 ],
112 ],
113 "last_changeset": {
113 "last_changeset": {
114 "author": "User <user@example.com>",
114 "author": "User <user@example.com>",
115 "branch": "default",
115 "branch": "default",
116 "date": "timestamp",
116 "date": "timestamp",
117 "message": "last commit message",
117 "message": "last commit message",
118 "parents": [
118 "parents": [
119 {
119 {
120 "raw_id": "commit-id"
120 "raw_id": "commit-id"
121 }
121 }
122 ],
122 ],
123 "raw_id": "commit-id",
123 "raw_id": "commit-id",
124 "revision": <revision number>,
124 "revision": <revision number>,
125 "short_id": "short id"
125 "short_id": "short id"
126 },
126 },
127 "lock_reason": null,
127 "lock_reason": null,
128 "locked_by": null,
128 "locked_by": null,
129 "locked_date": null,
129 "locked_date": null,
130 "owner": "owner-name",
130 "owner": "owner-name",
131 "permissions": [
131 "permissions": [
132 {
132 {
133 "name": "super-admin-name",
133 "name": "super-admin-name",
134 "origin": "super-admin",
134 "origin": "super-admin",
135 "permission": "repository.admin",
135 "permission": "repository.admin",
136 "type": "user"
136 "type": "user"
137 },
137 },
138 {
138 {
139 "name": "owner-name",
139 "name": "owner-name",
140 "origin": "owner",
140 "origin": "owner",
141 "permission": "repository.admin",
141 "permission": "repository.admin",
142 "type": "user"
142 "type": "user"
143 },
143 },
144 {
144 {
145 "name": "user-group-name",
145 "name": "user-group-name",
146 "origin": "permission",
146 "origin": "permission",
147 "permission": "repository.write",
147 "permission": "repository.write",
148 "type": "user_group"
148 "type": "user_group"
149 }
149 }
150 ],
150 ],
151 "private": true,
151 "private": true,
152 "repo_id": 676,
152 "repo_id": 676,
153 "repo_name": "user-group/repo-name",
153 "repo_name": "user-group/repo-name",
154 "repo_type": "hg"
154 "repo_type": "hg"
155 }
155 }
156 }
156 }
157 """
157 """
158
158
159 repo = get_repo_or_error(repoid)
159 repo = get_repo_or_error(repoid)
160 cache = Optional.extract(cache)
160 cache = Optional.extract(cache)
161
161
162 include_secrets = False
162 include_secrets = False
163 if has_superadmin_permission(apiuser):
163 if has_superadmin_permission(apiuser):
164 include_secrets = True
164 include_secrets = True
165 else:
165 else:
166 # check if we have at least read permission for this repo !
166 # check if we have at least read permission for this repo !
167 _perms = (
167 _perms = (
168 'repository.admin', 'repository.write', 'repository.read',)
168 'repository.admin', 'repository.write', 'repository.read',)
169 validate_repo_permissions(apiuser, repoid, repo, _perms)
169 validate_repo_permissions(apiuser, repoid, repo, _perms)
170
170
171 permissions = []
171 permissions = []
172 for _user in repo.permissions():
172 for _user in repo.permissions():
173 user_data = {
173 user_data = {
174 'name': _user.username,
174 'name': _user.username,
175 'permission': _user.permission,
175 'permission': _user.permission,
176 'origin': get_origin(_user),
176 'origin': get_origin(_user),
177 'type': "user",
177 'type': "user",
178 }
178 }
179 permissions.append(user_data)
179 permissions.append(user_data)
180
180
181 for _user_group in repo.permission_user_groups():
181 for _user_group in repo.permission_user_groups():
182 user_group_data = {
182 user_group_data = {
183 'name': _user_group.users_group_name,
183 'name': _user_group.users_group_name,
184 'permission': _user_group.permission,
184 'permission': _user_group.permission,
185 'origin': get_origin(_user_group),
185 'origin': get_origin(_user_group),
186 'type': "user_group",
186 'type': "user_group",
187 }
187 }
188 permissions.append(user_group_data)
188 permissions.append(user_group_data)
189
189
190 following_users = [
190 following_users = [
191 user.user.get_api_data(include_secrets=include_secrets)
191 user.user.get_api_data(include_secrets=include_secrets)
192 for user in repo.followers]
192 for user in repo.followers]
193
193
194 if not cache:
194 if not cache:
195 repo.update_commit_cache()
195 repo.update_commit_cache()
196 data = repo.get_api_data(include_secrets=include_secrets)
196 data = repo.get_api_data(include_secrets=include_secrets)
197 data['permissions'] = permissions
197 data['permissions'] = permissions
198 data['followers'] = following_users
198 data['followers'] = following_users
199 return data
199 return data
200
200
201
201
202 @jsonrpc_method()
202 @jsonrpc_method()
203 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
203 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
204 """
204 """
205 Lists all existing repositories.
205 Lists all existing repositories.
206
206
207 This command can only be run using an |authtoken| with admin rights,
207 This command can only be run using an |authtoken| with admin rights,
208 or users with at least read rights to |repos|.
208 or users with at least read rights to |repos|.
209
209
210 :param apiuser: This is filled automatically from the |authtoken|.
210 :param apiuser: This is filled automatically from the |authtoken|.
211 :type apiuser: AuthUser
211 :type apiuser: AuthUser
212 :param root: specify root repository group to fetch repositories.
212 :param root: specify root repository group to fetch repositories.
213 filters the returned repositories to be members of given root group.
213 filters the returned repositories to be members of given root group.
214 :type root: Optional(None)
214 :type root: Optional(None)
215 :param traverse: traverse given root into subrepositories. With this flag
215 :param traverse: traverse given root into subrepositories. With this flag
216 set to False, it will only return top-level repositories from `root`.
216 set to False, it will only return top-level repositories from `root`.
217 if root is empty it will return just top-level repositories.
217 if root is empty it will return just top-level repositories.
218 :type traverse: Optional(True)
218 :type traverse: Optional(True)
219
219
220
220
221 Example output:
221 Example output:
222
222
223 .. code-block:: bash
223 .. code-block:: bash
224
224
225 id : <id_given_in_input>
225 id : <id_given_in_input>
226 result: [
226 result: [
227 {
227 {
228 "repo_id" : "<repo_id>",
228 "repo_id" : "<repo_id>",
229 "repo_name" : "<reponame>"
229 "repo_name" : "<reponame>"
230 "repo_type" : "<repo_type>",
230 "repo_type" : "<repo_type>",
231 "clone_uri" : "<clone_uri>",
231 "clone_uri" : "<clone_uri>",
232 "private": : "<bool>",
232 "private": : "<bool>",
233 "created_on" : "<datetimecreated>",
233 "created_on" : "<datetimecreated>",
234 "description" : "<description>",
234 "description" : "<description>",
235 "landing_rev": "<landing_rev>",
235 "landing_rev": "<landing_rev>",
236 "owner": "<repo_owner>",
236 "owner": "<repo_owner>",
237 "fork_of": "<name_of_fork_parent>",
237 "fork_of": "<name_of_fork_parent>",
238 "enable_downloads": "<bool>",
238 "enable_downloads": "<bool>",
239 "enable_locking": "<bool>",
239 "enable_locking": "<bool>",
240 "enable_statistics": "<bool>",
240 "enable_statistics": "<bool>",
241 },
241 },
242 ...
242 ...
243 ]
243 ]
244 error: null
244 error: null
245 """
245 """
246
246
247 include_secrets = has_superadmin_permission(apiuser)
247 include_secrets = has_superadmin_permission(apiuser)
248 _perms = ('repository.read', 'repository.write', 'repository.admin',)
248 _perms = ('repository.read', 'repository.write', 'repository.admin',)
249 extras = {'user': apiuser}
249 extras = {'user': apiuser}
250
250
251 root = Optional.extract(root)
251 root = Optional.extract(root)
252 traverse = Optional.extract(traverse, binary=True)
252 traverse = Optional.extract(traverse, binary=True)
253
253
254 if root:
254 if root:
255 # verify parent existance, if it's empty return an error
255 # verify parent existance, if it's empty return an error
256 parent = RepoGroup.get_by_group_name(root)
256 parent = RepoGroup.get_by_group_name(root)
257 if not parent:
257 if not parent:
258 raise JSONRPCError(
258 raise JSONRPCError(
259 'Root repository group `{}` does not exist'.format(root))
259 'Root repository group `{}` does not exist'.format(root))
260
260
261 if traverse:
261 if traverse:
262 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
262 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
263 else:
263 else:
264 repos = RepoModel().get_repos_for_root(root=parent)
264 repos = RepoModel().get_repos_for_root(root=parent)
265 else:
265 else:
266 if traverse:
266 if traverse:
267 repos = RepoModel().get_all()
267 repos = RepoModel().get_all()
268 else:
268 else:
269 # return just top-level
269 # return just top-level
270 repos = RepoModel().get_repos_for_root(root=None)
270 repos = RepoModel().get_repos_for_root(root=None)
271
271
272 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
272 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
273 return [repo.get_api_data(include_secrets=include_secrets)
273 return [repo.get_api_data(include_secrets=include_secrets)
274 for repo in repo_list]
274 for repo in repo_list]
275
275
276
276
277 @jsonrpc_method()
277 @jsonrpc_method()
278 def get_repo_changeset(request, apiuser, repoid, revision,
278 def get_repo_changeset(request, apiuser, repoid, revision,
279 details=Optional('basic')):
279 details=Optional('basic')):
280 """
280 """
281 Returns information about a changeset.
281 Returns information about a changeset.
282
282
283 Additionally parameters define the amount of details returned by
283 Additionally parameters define the amount of details returned by
284 this function.
284 this function.
285
285
286 This command can only be run using an |authtoken| with admin rights,
286 This command can only be run using an |authtoken| with admin rights,
287 or users with at least read rights to the |repo|.
287 or users with at least read rights to the |repo|.
288
288
289 :param apiuser: This is filled automatically from the |authtoken|.
289 :param apiuser: This is filled automatically from the |authtoken|.
290 :type apiuser: AuthUser
290 :type apiuser: AuthUser
291 :param repoid: The repository name or repository id
291 :param repoid: The repository name or repository id
292 :type repoid: str or int
292 :type repoid: str or int
293 :param revision: revision for which listing should be done
293 :param revision: revision for which listing should be done
294 :type revision: str
294 :type revision: str
295 :param details: details can be 'basic|extended|full' full gives diff
295 :param details: details can be 'basic|extended|full' full gives diff
296 info details like the diff itself, and number of changed files etc.
296 info details like the diff itself, and number of changed files etc.
297 :type details: Optional(str)
297 :type details: Optional(str)
298
298
299 """
299 """
300 repo = get_repo_or_error(repoid)
300 repo = get_repo_or_error(repoid)
301 if not has_superadmin_permission(apiuser):
301 if not has_superadmin_permission(apiuser):
302 _perms = (
302 _perms = (
303 'repository.admin', 'repository.write', 'repository.read',)
303 'repository.admin', 'repository.write', 'repository.read',)
304 validate_repo_permissions(apiuser, repoid, repo, _perms)
304 validate_repo_permissions(apiuser, repoid, repo, _perms)
305
305
306 changes_details = Optional.extract(details)
306 changes_details = Optional.extract(details)
307 _changes_details_types = ['basic', 'extended', 'full']
307 _changes_details_types = ['basic', 'extended', 'full']
308 if changes_details not in _changes_details_types:
308 if changes_details not in _changes_details_types:
309 raise JSONRPCError(
309 raise JSONRPCError(
310 'ret_type must be one of %s' % (
310 'ret_type must be one of %s' % (
311 ','.join(_changes_details_types)))
311 ','.join(_changes_details_types)))
312
312
313 pre_load = ['author', 'branch', 'date', 'message', 'parents',
313 pre_load = ['author', 'branch', 'date', 'message', 'parents',
314 'status', '_commit', '_file_paths']
314 'status', '_commit', '_file_paths']
315
315
316 try:
316 try:
317 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
317 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
318 except TypeError as e:
318 except TypeError as e:
319 raise JSONRPCError(e.message)
319 raise JSONRPCError(e.message)
320 _cs_json = cs.__json__()
320 _cs_json = cs.__json__()
321 _cs_json['diff'] = build_commit_data(cs, changes_details)
321 _cs_json['diff'] = build_commit_data(cs, changes_details)
322 if changes_details == 'full':
322 if changes_details == 'full':
323 _cs_json['refs'] = cs._get_refs()
323 _cs_json['refs'] = cs._get_refs()
324 return _cs_json
324 return _cs_json
325
325
326
326
327 @jsonrpc_method()
327 @jsonrpc_method()
328 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
328 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
329 details=Optional('basic')):
329 details=Optional('basic')):
330 """
330 """
331 Returns a set of commits limited by the number starting
331 Returns a set of commits limited by the number starting
332 from the `start_rev` option.
332 from the `start_rev` option.
333
333
334 Additional parameters define the amount of details returned by this
334 Additional parameters define the amount of details returned by this
335 function.
335 function.
336
336
337 This command can only be run using an |authtoken| with admin rights,
337 This command can only be run using an |authtoken| with admin rights,
338 or users with at least read rights to |repos|.
338 or users with at least read rights to |repos|.
339
339
340 :param apiuser: This is filled automatically from the |authtoken|.
340 :param apiuser: This is filled automatically from the |authtoken|.
341 :type apiuser: AuthUser
341 :type apiuser: AuthUser
342 :param repoid: The repository name or repository ID.
342 :param repoid: The repository name or repository ID.
343 :type repoid: str or int
343 :type repoid: str or int
344 :param start_rev: The starting revision from where to get changesets.
344 :param start_rev: The starting revision from where to get changesets.
345 :type start_rev: str
345 :type start_rev: str
346 :param limit: Limit the number of commits to this amount
346 :param limit: Limit the number of commits to this amount
347 :type limit: str or int
347 :type limit: str or int
348 :param details: Set the level of detail returned. Valid option are:
348 :param details: Set the level of detail returned. Valid option are:
349 ``basic``, ``extended`` and ``full``.
349 ``basic``, ``extended`` and ``full``.
350 :type details: Optional(str)
350 :type details: Optional(str)
351
351
352 .. note::
352 .. note::
353
353
354 Setting the parameter `details` to the value ``full`` is extensive
354 Setting the parameter `details` to the value ``full`` is extensive
355 and returns details like the diff itself, and the number
355 and returns details like the diff itself, and the number
356 of changed files.
356 of changed files.
357
357
358 """
358 """
359 repo = get_repo_or_error(repoid)
359 repo = get_repo_or_error(repoid)
360 if not has_superadmin_permission(apiuser):
360 if not has_superadmin_permission(apiuser):
361 _perms = (
361 _perms = (
362 'repository.admin', 'repository.write', 'repository.read',)
362 'repository.admin', 'repository.write', 'repository.read',)
363 validate_repo_permissions(apiuser, repoid, repo, _perms)
363 validate_repo_permissions(apiuser, repoid, repo, _perms)
364
364
365 changes_details = Optional.extract(details)
365 changes_details = Optional.extract(details)
366 _changes_details_types = ['basic', 'extended', 'full']
366 _changes_details_types = ['basic', 'extended', 'full']
367 if changes_details not in _changes_details_types:
367 if changes_details not in _changes_details_types:
368 raise JSONRPCError(
368 raise JSONRPCError(
369 'ret_type must be one of %s' % (
369 'ret_type must be one of %s' % (
370 ','.join(_changes_details_types)))
370 ','.join(_changes_details_types)))
371
371
372 limit = int(limit)
372 limit = int(limit)
373 pre_load = ['author', 'branch', 'date', 'message', 'parents',
373 pre_load = ['author', 'branch', 'date', 'message', 'parents',
374 'status', '_commit', '_file_paths']
374 'status', '_commit', '_file_paths']
375
375
376 vcs_repo = repo.scm_instance()
376 vcs_repo = repo.scm_instance()
377 # SVN needs a special case to distinguish its index and commit id
377 # SVN needs a special case to distinguish its index and commit id
378 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
378 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
379 start_rev = vcs_repo.commit_ids[0]
379 start_rev = vcs_repo.commit_ids[0]
380
380
381 try:
381 try:
382 commits = vcs_repo.get_commits(
382 commits = vcs_repo.get_commits(
383 start_id=start_rev, pre_load=pre_load)
383 start_id=start_rev, pre_load=pre_load)
384 except TypeError as e:
384 except TypeError as e:
385 raise JSONRPCError(e.message)
385 raise JSONRPCError(e.message)
386 except Exception:
386 except Exception:
387 log.exception('Fetching of commits failed')
387 log.exception('Fetching of commits failed')
388 raise JSONRPCError('Error occurred during commit fetching')
388 raise JSONRPCError('Error occurred during commit fetching')
389
389
390 ret = []
390 ret = []
391 for cnt, commit in enumerate(commits):
391 for cnt, commit in enumerate(commits):
392 if cnt >= limit != -1:
392 if cnt >= limit != -1:
393 break
393 break
394 _cs_json = commit.__json__()
394 _cs_json = commit.__json__()
395 _cs_json['diff'] = build_commit_data(commit, changes_details)
395 _cs_json['diff'] = build_commit_data(commit, changes_details)
396 if changes_details == 'full':
396 if changes_details == 'full':
397 _cs_json['refs'] = {
397 _cs_json['refs'] = {
398 'branches': [commit.branch],
398 'branches': [commit.branch],
399 'bookmarks': getattr(commit, 'bookmarks', []),
399 'bookmarks': getattr(commit, 'bookmarks', []),
400 'tags': commit.tags
400 'tags': commit.tags
401 }
401 }
402 ret.append(_cs_json)
402 ret.append(_cs_json)
403 return ret
403 return ret
404
404
405
405
406 @jsonrpc_method()
406 @jsonrpc_method()
407 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
407 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
408 ret_type=Optional('all'), details=Optional('basic'),
408 ret_type=Optional('all'), details=Optional('basic'),
409 max_file_bytes=Optional(None)):
409 max_file_bytes=Optional(None)):
410 """
410 """
411 Returns a list of nodes and children in a flat list for a given
411 Returns a list of nodes and children in a flat list for a given
412 path at given revision.
412 path at given revision.
413
413
414 It's possible to specify ret_type to show only `files` or `dirs`.
414 It's possible to specify ret_type to show only `files` or `dirs`.
415
415
416 This command can only be run using an |authtoken| with admin rights,
416 This command can only be run using an |authtoken| with admin rights,
417 or users with at least read rights to |repos|.
417 or users with at least read rights to |repos|.
418
418
419 :param apiuser: This is filled automatically from the |authtoken|.
419 :param apiuser: This is filled automatically from the |authtoken|.
420 :type apiuser: AuthUser
420 :type apiuser: AuthUser
421 :param repoid: The repository name or repository ID.
421 :param repoid: The repository name or repository ID.
422 :type repoid: str or int
422 :type repoid: str or int
423 :param revision: The revision for which listing should be done.
423 :param revision: The revision for which listing should be done.
424 :type revision: str
424 :type revision: str
425 :param root_path: The path from which to start displaying.
425 :param root_path: The path from which to start displaying.
426 :type root_path: str
426 :type root_path: str
427 :param ret_type: Set the return type. Valid options are
427 :param ret_type: Set the return type. Valid options are
428 ``all`` (default), ``files`` and ``dirs``.
428 ``all`` (default), ``files`` and ``dirs``.
429 :type ret_type: Optional(str)
429 :type ret_type: Optional(str)
430 :param details: Returns extended information about nodes, such as
430 :param details: Returns extended information about nodes, such as
431 md5, binary, and or content. The valid options are ``basic`` and
431 md5, binary, and or content. The valid options are ``basic`` and
432 ``full``.
432 ``full``.
433 :type details: Optional(str)
433 :type details: Optional(str)
434 :param max_file_bytes: Only return file content under this file size bytes
434 :param max_file_bytes: Only return file content under this file size bytes
435 :type details: Optional(int)
435 :type details: Optional(int)
436
436
437 Example output:
437 Example output:
438
438
439 .. code-block:: bash
439 .. code-block:: bash
440
440
441 id : <id_given_in_input>
441 id : <id_given_in_input>
442 result: [
442 result: [
443 {
443 {
444 "name" : "<name>"
444 "name" : "<name>"
445 "type" : "<type>",
445 "type" : "<type>",
446 "binary": "<true|false>" (only in extended mode)
446 "binary": "<true|false>" (only in extended mode)
447 "md5" : "<md5 of file content>" (only in extended mode)
447 "md5" : "<md5 of file content>" (only in extended mode)
448 },
448 },
449 ...
449 ...
450 ]
450 ]
451 error: null
451 error: null
452 """
452 """
453
453
454 repo = get_repo_or_error(repoid)
454 repo = get_repo_or_error(repoid)
455 if not has_superadmin_permission(apiuser):
455 if not has_superadmin_permission(apiuser):
456 _perms = (
456 _perms = (
457 'repository.admin', 'repository.write', 'repository.read',)
457 'repository.admin', 'repository.write', 'repository.read',)
458 validate_repo_permissions(apiuser, repoid, repo, _perms)
458 validate_repo_permissions(apiuser, repoid, repo, _perms)
459
459
460 ret_type = Optional.extract(ret_type)
460 ret_type = Optional.extract(ret_type)
461 details = Optional.extract(details)
461 details = Optional.extract(details)
462 _extended_types = ['basic', 'full']
462 _extended_types = ['basic', 'full']
463 if details not in _extended_types:
463 if details not in _extended_types:
464 raise JSONRPCError(
464 raise JSONRPCError(
465 'ret_type must be one of %s' % (','.join(_extended_types)))
465 'ret_type must be one of %s' % (','.join(_extended_types)))
466 extended_info = False
466 extended_info = False
467 content = False
467 content = False
468 if details == 'basic':
468 if details == 'basic':
469 extended_info = True
469 extended_info = True
470
470
471 if details == 'full':
471 if details == 'full':
472 extended_info = content = True
472 extended_info = content = True
473
473
474 _map = {}
474 _map = {}
475 try:
475 try:
476 # check if repo is not empty by any chance, skip quicker if it is.
476 # check if repo is not empty by any chance, skip quicker if it is.
477 _scm = repo.scm_instance()
477 _scm = repo.scm_instance()
478 if _scm.is_empty():
478 if _scm.is_empty():
479 return []
479 return []
480
480
481 _d, _f = ScmModel().get_nodes(
481 _d, _f = ScmModel().get_nodes(
482 repo, revision, root_path, flat=False,
482 repo, revision, root_path, flat=False,
483 extended_info=extended_info, content=content,
483 extended_info=extended_info, content=content,
484 max_file_bytes=max_file_bytes)
484 max_file_bytes=max_file_bytes)
485 _map = {
485 _map = {
486 'all': _d + _f,
486 'all': _d + _f,
487 'files': _f,
487 'files': _f,
488 'dirs': _d,
488 'dirs': _d,
489 }
489 }
490 return _map[ret_type]
490 return _map[ret_type]
491 except KeyError:
491 except KeyError:
492 raise JSONRPCError(
492 raise JSONRPCError(
493 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
493 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
494 except Exception:
494 except Exception:
495 log.exception("Exception occurred while trying to get repo nodes")
495 log.exception("Exception occurred while trying to get repo nodes")
496 raise JSONRPCError(
496 raise JSONRPCError(
497 'failed to get repo: `%s` nodes' % repo.repo_name
497 'failed to get repo: `%s` nodes' % repo.repo_name
498 )
498 )
499
499
500
500
501 @jsonrpc_method()
501 @jsonrpc_method()
502 def get_repo_refs(request, apiuser, repoid):
502 def get_repo_refs(request, apiuser, repoid):
503 """
503 """
504 Returns a dictionary of current references. It returns
504 Returns a dictionary of current references. It returns
505 bookmarks, branches, closed_branches, and tags for given repository
505 bookmarks, branches, closed_branches, and tags for given repository
506
506
507 It's possible to specify ret_type to show only `files` or `dirs`.
507 It's possible to specify ret_type to show only `files` or `dirs`.
508
508
509 This command can only be run using an |authtoken| with admin rights,
509 This command can only be run using an |authtoken| with admin rights,
510 or users with at least read rights to |repos|.
510 or users with at least read rights to |repos|.
511
511
512 :param apiuser: This is filled automatically from the |authtoken|.
512 :param apiuser: This is filled automatically from the |authtoken|.
513 :type apiuser: AuthUser
513 :type apiuser: AuthUser
514 :param repoid: The repository name or repository ID.
514 :param repoid: The repository name or repository ID.
515 :type repoid: str or int
515 :type repoid: str or int
516
516
517 Example output:
517 Example output:
518
518
519 .. code-block:: bash
519 .. code-block:: bash
520
520
521 id : <id_given_in_input>
521 id : <id_given_in_input>
522 "result": {
522 "result": {
523 "bookmarks": {
523 "bookmarks": {
524 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
524 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
525 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
525 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
526 },
526 },
527 "branches": {
527 "branches": {
528 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
528 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
529 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
529 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
530 },
530 },
531 "branches_closed": {},
531 "branches_closed": {},
532 "tags": {
532 "tags": {
533 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
533 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
534 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
534 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
535 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
535 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
536 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
536 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
537 }
537 }
538 }
538 }
539 error: null
539 error: null
540 """
540 """
541
541
542 repo = get_repo_or_error(repoid)
542 repo = get_repo_or_error(repoid)
543 if not has_superadmin_permission(apiuser):
543 if not has_superadmin_permission(apiuser):
544 _perms = ('repository.admin', 'repository.write', 'repository.read',)
544 _perms = ('repository.admin', 'repository.write', 'repository.read',)
545 validate_repo_permissions(apiuser, repoid, repo, _perms)
545 validate_repo_permissions(apiuser, repoid, repo, _perms)
546
546
547 try:
547 try:
548 # check if repo is not empty by any chance, skip quicker if it is.
548 # check if repo is not empty by any chance, skip quicker if it is.
549 vcs_instance = repo.scm_instance()
549 vcs_instance = repo.scm_instance()
550 refs = vcs_instance.refs()
550 refs = vcs_instance.refs()
551 return refs
551 return refs
552 except Exception:
552 except Exception:
553 log.exception("Exception occurred while trying to get repo refs")
553 log.exception("Exception occurred while trying to get repo refs")
554 raise JSONRPCError(
554 raise JSONRPCError(
555 'failed to get repo: `%s` references' % repo.repo_name
555 'failed to get repo: `%s` references' % repo.repo_name
556 )
556 )
557
557
558
558
559 @jsonrpc_method()
559 @jsonrpc_method()
560 def create_repo(
560 def create_repo(
561 request, apiuser, repo_name, repo_type,
561 request, apiuser, repo_name, repo_type,
562 owner=Optional(OAttr('apiuser')),
562 owner=Optional(OAttr('apiuser')),
563 description=Optional(''),
563 description=Optional(''),
564 private=Optional(False),
564 private=Optional(False),
565 clone_uri=Optional(None),
565 clone_uri=Optional(None),
566 landing_rev=Optional('rev:tip'),
566 landing_rev=Optional('rev:tip'),
567 enable_statistics=Optional(False),
567 enable_statistics=Optional(False),
568 enable_locking=Optional(False),
568 enable_locking=Optional(False),
569 enable_downloads=Optional(False),
569 enable_downloads=Optional(False),
570 copy_permissions=Optional(False)):
570 copy_permissions=Optional(False)):
571 """
571 """
572 Creates a repository.
572 Creates a repository.
573
573
574 * If the repository name contains "/", repository will be created inside
574 * If the repository name contains "/", repository will be created inside
575 a repository group or nested repository groups
575 a repository group or nested repository groups
576
576
577 For example "foo/bar/repo1" will create |repo| called "repo1" inside
577 For example "foo/bar/repo1" will create |repo| called "repo1" inside
578 group "foo/bar". You have to have permissions to access and write to
578 group "foo/bar". You have to have permissions to access and write to
579 the last repository group ("bar" in this example)
579 the last repository group ("bar" in this example)
580
580
581 This command can only be run using an |authtoken| with at least
581 This command can only be run using an |authtoken| with at least
582 permissions to create repositories, or write permissions to
582 permissions to create repositories, or write permissions to
583 parent repository groups.
583 parent repository groups.
584
584
585 :param apiuser: This is filled automatically from the |authtoken|.
585 :param apiuser: This is filled automatically from the |authtoken|.
586 :type apiuser: AuthUser
586 :type apiuser: AuthUser
587 :param repo_name: Set the repository name.
587 :param repo_name: Set the repository name.
588 :type repo_name: str
588 :type repo_name: str
589 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
589 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
590 :type repo_type: str
590 :type repo_type: str
591 :param owner: user_id or username
591 :param owner: user_id or username
592 :type owner: Optional(str)
592 :type owner: Optional(str)
593 :param description: Set the repository description.
593 :param description: Set the repository description.
594 :type description: Optional(str)
594 :type description: Optional(str)
595 :param private: set repository as private
595 :param private: set repository as private
596 :type private: bool
596 :type private: bool
597 :param clone_uri: set clone_uri
597 :param clone_uri: set clone_uri
598 :type clone_uri: str
598 :type clone_uri: str
599 :param landing_rev: <rev_type>:<rev>
599 :param landing_rev: <rev_type>:<rev>
600 :type landing_rev: str
600 :type landing_rev: str
601 :param enable_locking:
601 :param enable_locking:
602 :type enable_locking: bool
602 :type enable_locking: bool
603 :param enable_downloads:
603 :param enable_downloads:
604 :type enable_downloads: bool
604 :type enable_downloads: bool
605 :param enable_statistics:
605 :param enable_statistics:
606 :type enable_statistics: bool
606 :type enable_statistics: bool
607 :param copy_permissions: Copy permission from group in which the
607 :param copy_permissions: Copy permission from group in which the
608 repository is being created.
608 repository is being created.
609 :type copy_permissions: bool
609 :type copy_permissions: bool
610
610
611
611
612 Example output:
612 Example output:
613
613
614 .. code-block:: bash
614 .. code-block:: bash
615
615
616 id : <id_given_in_input>
616 id : <id_given_in_input>
617 result: {
617 result: {
618 "msg": "Created new repository `<reponame>`",
618 "msg": "Created new repository `<reponame>`",
619 "success": true,
619 "success": true,
620 "task": "<celery task id or None if done sync>"
620 "task": "<celery task id or None if done sync>"
621 }
621 }
622 error: null
622 error: null
623
623
624
624
625 Example error output:
625 Example error output:
626
626
627 .. code-block:: bash
627 .. code-block:: bash
628
628
629 id : <id_given_in_input>
629 id : <id_given_in_input>
630 result : null
630 result : null
631 error : {
631 error : {
632 'failed to create repository `<repo_name>`'
632 'failed to create repository `<repo_name>`'
633 }
633 }
634
634
635 """
635 """
636
636
637 owner = validate_set_owner_permissions(apiuser, owner)
637 owner = validate_set_owner_permissions(apiuser, owner)
638
638
639 description = Optional.extract(description)
639 description = Optional.extract(description)
640 copy_permissions = Optional.extract(copy_permissions)
640 copy_permissions = Optional.extract(copy_permissions)
641 clone_uri = Optional.extract(clone_uri)
641 clone_uri = Optional.extract(clone_uri)
642 landing_commit_ref = Optional.extract(landing_rev)
642 landing_commit_ref = Optional.extract(landing_rev)
643
643
644 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
644 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
645 if isinstance(private, Optional):
645 if isinstance(private, Optional):
646 private = defs.get('repo_private') or Optional.extract(private)
646 private = defs.get('repo_private') or Optional.extract(private)
647 if isinstance(repo_type, Optional):
647 if isinstance(repo_type, Optional):
648 repo_type = defs.get('repo_type')
648 repo_type = defs.get('repo_type')
649 if isinstance(enable_statistics, Optional):
649 if isinstance(enable_statistics, Optional):
650 enable_statistics = defs.get('repo_enable_statistics')
650 enable_statistics = defs.get('repo_enable_statistics')
651 if isinstance(enable_locking, Optional):
651 if isinstance(enable_locking, Optional):
652 enable_locking = defs.get('repo_enable_locking')
652 enable_locking = defs.get('repo_enable_locking')
653 if isinstance(enable_downloads, Optional):
653 if isinstance(enable_downloads, Optional):
654 enable_downloads = defs.get('repo_enable_downloads')
654 enable_downloads = defs.get('repo_enable_downloads')
655
655
656 schema = repo_schema.RepoSchema().bind(
656 schema = repo_schema.RepoSchema().bind(
657 repo_type_options=rhodecode.BACKENDS.keys(),
657 repo_type_options=rhodecode.BACKENDS.keys(),
658 # user caller
658 # user caller
659 user=apiuser)
659 user=apiuser)
660
660
661 try:
661 try:
662 schema_data = schema.deserialize(dict(
662 schema_data = schema.deserialize(dict(
663 repo_name=repo_name,
663 repo_name=repo_name,
664 repo_type=repo_type,
664 repo_type=repo_type,
665 repo_owner=owner.username,
665 repo_owner=owner.username,
666 repo_description=description,
666 repo_description=description,
667 repo_landing_commit_ref=landing_commit_ref,
667 repo_landing_commit_ref=landing_commit_ref,
668 repo_clone_uri=clone_uri,
668 repo_clone_uri=clone_uri,
669 repo_private=private,
669 repo_private=private,
670 repo_copy_permissions=copy_permissions,
670 repo_copy_permissions=copy_permissions,
671 repo_enable_statistics=enable_statistics,
671 repo_enable_statistics=enable_statistics,
672 repo_enable_downloads=enable_downloads,
672 repo_enable_downloads=enable_downloads,
673 repo_enable_locking=enable_locking))
673 repo_enable_locking=enable_locking))
674 except validation_schema.Invalid as err:
674 except validation_schema.Invalid as err:
675 raise JSONRPCValidationError(colander_exc=err)
675 raise JSONRPCValidationError(colander_exc=err)
676
676
677 try:
677 try:
678 data = {
678 data = {
679 'owner': owner,
679 'owner': owner,
680 'repo_name': schema_data['repo_group']['repo_name_without_group'],
680 'repo_name': schema_data['repo_group']['repo_name_without_group'],
681 'repo_name_full': schema_data['repo_name'],
681 'repo_name_full': schema_data['repo_name'],
682 'repo_group': schema_data['repo_group']['repo_group_id'],
682 'repo_group': schema_data['repo_group']['repo_group_id'],
683 'repo_type': schema_data['repo_type'],
683 'repo_type': schema_data['repo_type'],
684 'repo_description': schema_data['repo_description'],
684 'repo_description': schema_data['repo_description'],
685 'repo_private': schema_data['repo_private'],
685 'repo_private': schema_data['repo_private'],
686 'clone_uri': schema_data['repo_clone_uri'],
686 'clone_uri': schema_data['repo_clone_uri'],
687 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
687 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
688 'enable_statistics': schema_data['repo_enable_statistics'],
688 'enable_statistics': schema_data['repo_enable_statistics'],
689 'enable_locking': schema_data['repo_enable_locking'],
689 'enable_locking': schema_data['repo_enable_locking'],
690 'enable_downloads': schema_data['repo_enable_downloads'],
690 'enable_downloads': schema_data['repo_enable_downloads'],
691 'repo_copy_permissions': schema_data['repo_copy_permissions'],
691 'repo_copy_permissions': schema_data['repo_copy_permissions'],
692 }
692 }
693
693
694 task = RepoModel().create(form_data=data, cur_user=owner)
694 task = RepoModel().create(form_data=data, cur_user=owner)
695 task_id = get_task_id(task)
695 task_id = get_task_id(task)
696 # no commit, it's done in RepoModel, or async via celery
696 # no commit, it's done in RepoModel, or async via celery
697 return {
697 return {
698 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
698 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
699 'success': True, # cannot return the repo data here since fork
699 'success': True, # cannot return the repo data here since fork
700 # can be done async
700 # can be done async
701 'task': task_id
701 'task': task_id
702 }
702 }
703 except Exception:
703 except Exception:
704 log.exception(
704 log.exception(
705 u"Exception while trying to create the repository %s",
705 u"Exception while trying to create the repository %s",
706 schema_data['repo_name'])
706 schema_data['repo_name'])
707 raise JSONRPCError(
707 raise JSONRPCError(
708 'failed to create repository `%s`' % (schema_data['repo_name'],))
708 'failed to create repository `%s`' % (schema_data['repo_name'],))
709
709
710
710
711 @jsonrpc_method()
711 @jsonrpc_method()
712 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
712 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
713 description=Optional('')):
713 description=Optional('')):
714 """
714 """
715 Adds an extra field to a repository.
715 Adds an extra field to a repository.
716
716
717 This command can only be run using an |authtoken| with at least
717 This command can only be run using an |authtoken| with at least
718 write permissions to the |repo|.
718 write permissions to the |repo|.
719
719
720 :param apiuser: This is filled automatically from the |authtoken|.
720 :param apiuser: This is filled automatically from the |authtoken|.
721 :type apiuser: AuthUser
721 :type apiuser: AuthUser
722 :param repoid: Set the repository name or repository id.
722 :param repoid: Set the repository name or repository id.
723 :type repoid: str or int
723 :type repoid: str or int
724 :param key: Create a unique field key for this repository.
724 :param key: Create a unique field key for this repository.
725 :type key: str
725 :type key: str
726 :param label:
726 :param label:
727 :type label: Optional(str)
727 :type label: Optional(str)
728 :param description:
728 :param description:
729 :type description: Optional(str)
729 :type description: Optional(str)
730 """
730 """
731 repo = get_repo_or_error(repoid)
731 repo = get_repo_or_error(repoid)
732 if not has_superadmin_permission(apiuser):
732 if not has_superadmin_permission(apiuser):
733 _perms = ('repository.admin',)
733 _perms = ('repository.admin',)
734 validate_repo_permissions(apiuser, repoid, repo, _perms)
734 validate_repo_permissions(apiuser, repoid, repo, _perms)
735
735
736 label = Optional.extract(label) or key
736 label = Optional.extract(label) or key
737 description = Optional.extract(description)
737 description = Optional.extract(description)
738
738
739 field = RepositoryField.get_by_key_name(key, repo)
739 field = RepositoryField.get_by_key_name(key, repo)
740 if field:
740 if field:
741 raise JSONRPCError('Field with key '
741 raise JSONRPCError('Field with key '
742 '`%s` exists for repo `%s`' % (key, repoid))
742 '`%s` exists for repo `%s`' % (key, repoid))
743
743
744 try:
744 try:
745 RepoModel().add_repo_field(repo, key, field_label=label,
745 RepoModel().add_repo_field(repo, key, field_label=label,
746 field_desc=description)
746 field_desc=description)
747 Session().commit()
747 Session().commit()
748 return {
748 return {
749 'msg': "Added new repository field `%s`" % (key,),
749 'msg': "Added new repository field `%s`" % (key,),
750 'success': True,
750 'success': True,
751 }
751 }
752 except Exception:
752 except Exception:
753 log.exception("Exception occurred while trying to add field to repo")
753 log.exception("Exception occurred while trying to add field to repo")
754 raise JSONRPCError(
754 raise JSONRPCError(
755 'failed to create new field for repository `%s`' % (repoid,))
755 'failed to create new field for repository `%s`' % (repoid,))
756
756
757
757
758 @jsonrpc_method()
758 @jsonrpc_method()
759 def remove_field_from_repo(request, apiuser, repoid, key):
759 def remove_field_from_repo(request, apiuser, repoid, key):
760 """
760 """
761 Removes an extra field from a repository.
761 Removes an extra field from a repository.
762
762
763 This command can only be run using an |authtoken| with at least
763 This command can only be run using an |authtoken| with at least
764 write permissions to the |repo|.
764 write permissions to the |repo|.
765
765
766 :param apiuser: This is filled automatically from the |authtoken|.
766 :param apiuser: This is filled automatically from the |authtoken|.
767 :type apiuser: AuthUser
767 :type apiuser: AuthUser
768 :param repoid: Set the repository name or repository ID.
768 :param repoid: Set the repository name or repository ID.
769 :type repoid: str or int
769 :type repoid: str or int
770 :param key: Set the unique field key for this repository.
770 :param key: Set the unique field key for this repository.
771 :type key: str
771 :type key: str
772 """
772 """
773
773
774 repo = get_repo_or_error(repoid)
774 repo = get_repo_or_error(repoid)
775 if not has_superadmin_permission(apiuser):
775 if not has_superadmin_permission(apiuser):
776 _perms = ('repository.admin',)
776 _perms = ('repository.admin',)
777 validate_repo_permissions(apiuser, repoid, repo, _perms)
777 validate_repo_permissions(apiuser, repoid, repo, _perms)
778
778
779 field = RepositoryField.get_by_key_name(key, repo)
779 field = RepositoryField.get_by_key_name(key, repo)
780 if not field:
780 if not field:
781 raise JSONRPCError('Field with key `%s` does not '
781 raise JSONRPCError('Field with key `%s` does not '
782 'exists for repo `%s`' % (key, repoid))
782 'exists for repo `%s`' % (key, repoid))
783
783
784 try:
784 try:
785 RepoModel().delete_repo_field(repo, field_key=key)
785 RepoModel().delete_repo_field(repo, field_key=key)
786 Session().commit()
786 Session().commit()
787 return {
787 return {
788 'msg': "Deleted repository field `%s`" % (key,),
788 'msg': "Deleted repository field `%s`" % (key,),
789 'success': True,
789 'success': True,
790 }
790 }
791 except Exception:
791 except Exception:
792 log.exception(
792 log.exception(
793 "Exception occurred while trying to delete field from repo")
793 "Exception occurred while trying to delete field from repo")
794 raise JSONRPCError(
794 raise JSONRPCError(
795 'failed to delete field for repository `%s`' % (repoid,))
795 'failed to delete field for repository `%s`' % (repoid,))
796
796
797
797
798 @jsonrpc_method()
798 @jsonrpc_method()
799 def update_repo(
799 def update_repo(
800 request, apiuser, repoid, repo_name=Optional(None),
800 request, apiuser, repoid, repo_name=Optional(None),
801 owner=Optional(OAttr('apiuser')), description=Optional(''),
801 owner=Optional(OAttr('apiuser')), description=Optional(''),
802 private=Optional(False), clone_uri=Optional(None),
802 private=Optional(False), clone_uri=Optional(None),
803 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
803 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
804 enable_statistics=Optional(False),
804 enable_statistics=Optional(False),
805 enable_locking=Optional(False),
805 enable_locking=Optional(False),
806 enable_downloads=Optional(False), fields=Optional('')):
806 enable_downloads=Optional(False), fields=Optional('')):
807 """
807 """
808 Updates a repository with the given information.
808 Updates a repository with the given information.
809
809
810 This command can only be run using an |authtoken| with at least
810 This command can only be run using an |authtoken| with at least
811 admin permissions to the |repo|.
811 admin permissions to the |repo|.
812
812
813 * If the repository name contains "/", repository will be updated
813 * If the repository name contains "/", repository will be updated
814 accordingly with a repository group or nested repository groups
814 accordingly with a repository group or nested repository groups
815
815
816 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
816 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
817 called "repo-test" and place it inside group "foo/bar".
817 called "repo-test" and place it inside group "foo/bar".
818 You have to have permissions to access and write to the last repository
818 You have to have permissions to access and write to the last repository
819 group ("bar" in this example)
819 group ("bar" in this example)
820
820
821 :param apiuser: This is filled automatically from the |authtoken|.
821 :param apiuser: This is filled automatically from the |authtoken|.
822 :type apiuser: AuthUser
822 :type apiuser: AuthUser
823 :param repoid: repository name or repository ID.
823 :param repoid: repository name or repository ID.
824 :type repoid: str or int
824 :type repoid: str or int
825 :param repo_name: Update the |repo| name, including the
825 :param repo_name: Update the |repo| name, including the
826 repository group it's in.
826 repository group it's in.
827 :type repo_name: str
827 :type repo_name: str
828 :param owner: Set the |repo| owner.
828 :param owner: Set the |repo| owner.
829 :type owner: str
829 :type owner: str
830 :param fork_of: Set the |repo| as fork of another |repo|.
830 :param fork_of: Set the |repo| as fork of another |repo|.
831 :type fork_of: str
831 :type fork_of: str
832 :param description: Update the |repo| description.
832 :param description: Update the |repo| description.
833 :type description: str
833 :type description: str
834 :param private: Set the |repo| as private. (True | False)
834 :param private: Set the |repo| as private. (True | False)
835 :type private: bool
835 :type private: bool
836 :param clone_uri: Update the |repo| clone URI.
836 :param clone_uri: Update the |repo| clone URI.
837 :type clone_uri: str
837 :type clone_uri: str
838 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
838 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
839 :type landing_rev: str
839 :type landing_rev: str
840 :param enable_statistics: Enable statistics on the |repo|, (True | False).
840 :param enable_statistics: Enable statistics on the |repo|, (True | False).
841 :type enable_statistics: bool
841 :type enable_statistics: bool
842 :param enable_locking: Enable |repo| locking.
842 :param enable_locking: Enable |repo| locking.
843 :type enable_locking: bool
843 :type enable_locking: bool
844 :param enable_downloads: Enable downloads from the |repo|, (True | False).
844 :param enable_downloads: Enable downloads from the |repo|, (True | False).
845 :type enable_downloads: bool
845 :type enable_downloads: bool
846 :param fields: Add extra fields to the |repo|. Use the following
846 :param fields: Add extra fields to the |repo|. Use the following
847 example format: ``field_key=field_val,field_key2=fieldval2``.
847 example format: ``field_key=field_val,field_key2=fieldval2``.
848 Escape ', ' with \,
848 Escape ', ' with \,
849 :type fields: str
849 :type fields: str
850 """
850 """
851
851
852 repo = get_repo_or_error(repoid)
852 repo = get_repo_or_error(repoid)
853
853
854 include_secrets = False
854 include_secrets = False
855 if not has_superadmin_permission(apiuser):
855 if not has_superadmin_permission(apiuser):
856 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
856 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
857 else:
857 else:
858 include_secrets = True
858 include_secrets = True
859
859
860 updates = dict(
860 updates = dict(
861 repo_name=repo_name
861 repo_name=repo_name
862 if not isinstance(repo_name, Optional) else repo.repo_name,
862 if not isinstance(repo_name, Optional) else repo.repo_name,
863
863
864 fork_id=fork_of
864 fork_id=fork_of
865 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
865 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
866
866
867 user=owner
867 user=owner
868 if not isinstance(owner, Optional) else repo.user.username,
868 if not isinstance(owner, Optional) else repo.user.username,
869
869
870 repo_description=description
870 repo_description=description
871 if not isinstance(description, Optional) else repo.description,
871 if not isinstance(description, Optional) else repo.description,
872
872
873 repo_private=private
873 repo_private=private
874 if not isinstance(private, Optional) else repo.private,
874 if not isinstance(private, Optional) else repo.private,
875
875
876 clone_uri=clone_uri
876 clone_uri=clone_uri
877 if not isinstance(clone_uri, Optional) else repo.clone_uri,
877 if not isinstance(clone_uri, Optional) else repo.clone_uri,
878
878
879 repo_landing_rev=landing_rev
879 repo_landing_rev=landing_rev
880 if not isinstance(landing_rev, Optional) else repo._landing_revision,
880 if not isinstance(landing_rev, Optional) else repo._landing_revision,
881
881
882 repo_enable_statistics=enable_statistics
882 repo_enable_statistics=enable_statistics
883 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
883 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
884
884
885 repo_enable_locking=enable_locking
885 repo_enable_locking=enable_locking
886 if not isinstance(enable_locking, Optional) else repo.enable_locking,
886 if not isinstance(enable_locking, Optional) else repo.enable_locking,
887
887
888 repo_enable_downloads=enable_downloads
888 repo_enable_downloads=enable_downloads
889 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
889 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
890
890
891 ref_choices, _labels = ScmModel().get_repo_landing_revs(
891 ref_choices, _labels = ScmModel().get_repo_landing_revs(
892 request.translate, repo=repo)
892 request.translate, repo=repo)
893
893
894 old_values = repo.get_api_data()
894 old_values = repo.get_api_data()
895 schema = repo_schema.RepoSchema().bind(
895 schema = repo_schema.RepoSchema().bind(
896 repo_type_options=rhodecode.BACKENDS.keys(),
896 repo_type_options=rhodecode.BACKENDS.keys(),
897 repo_ref_options=ref_choices,
897 repo_ref_options=ref_choices,
898 # user caller
898 # user caller
899 user=apiuser,
899 user=apiuser,
900 old_values=old_values)
900 old_values=old_values)
901 try:
901 try:
902 schema_data = schema.deserialize(dict(
902 schema_data = schema.deserialize(dict(
903 # we save old value, users cannot change type
903 # we save old value, users cannot change type
904 repo_type=repo.repo_type,
904 repo_type=repo.repo_type,
905
905
906 repo_name=updates['repo_name'],
906 repo_name=updates['repo_name'],
907 repo_owner=updates['user'],
907 repo_owner=updates['user'],
908 repo_description=updates['repo_description'],
908 repo_description=updates['repo_description'],
909 repo_clone_uri=updates['clone_uri'],
909 repo_clone_uri=updates['clone_uri'],
910 repo_fork_of=updates['fork_id'],
910 repo_fork_of=updates['fork_id'],
911 repo_private=updates['repo_private'],
911 repo_private=updates['repo_private'],
912 repo_landing_commit_ref=updates['repo_landing_rev'],
912 repo_landing_commit_ref=updates['repo_landing_rev'],
913 repo_enable_statistics=updates['repo_enable_statistics'],
913 repo_enable_statistics=updates['repo_enable_statistics'],
914 repo_enable_downloads=updates['repo_enable_downloads'],
914 repo_enable_downloads=updates['repo_enable_downloads'],
915 repo_enable_locking=updates['repo_enable_locking']))
915 repo_enable_locking=updates['repo_enable_locking']))
916 except validation_schema.Invalid as err:
916 except validation_schema.Invalid as err:
917 raise JSONRPCValidationError(colander_exc=err)
917 raise JSONRPCValidationError(colander_exc=err)
918
918
919 # save validated data back into the updates dict
919 # save validated data back into the updates dict
920 validated_updates = dict(
920 validated_updates = dict(
921 repo_name=schema_data['repo_group']['repo_name_without_group'],
921 repo_name=schema_data['repo_group']['repo_name_without_group'],
922 repo_group=schema_data['repo_group']['repo_group_id'],
922 repo_group=schema_data['repo_group']['repo_group_id'],
923
923
924 user=schema_data['repo_owner'],
924 user=schema_data['repo_owner'],
925 repo_description=schema_data['repo_description'],
925 repo_description=schema_data['repo_description'],
926 repo_private=schema_data['repo_private'],
926 repo_private=schema_data['repo_private'],
927 clone_uri=schema_data['repo_clone_uri'],
927 clone_uri=schema_data['repo_clone_uri'],
928 repo_landing_rev=schema_data['repo_landing_commit_ref'],
928 repo_landing_rev=schema_data['repo_landing_commit_ref'],
929 repo_enable_statistics=schema_data['repo_enable_statistics'],
929 repo_enable_statistics=schema_data['repo_enable_statistics'],
930 repo_enable_locking=schema_data['repo_enable_locking'],
930 repo_enable_locking=schema_data['repo_enable_locking'],
931 repo_enable_downloads=schema_data['repo_enable_downloads'],
931 repo_enable_downloads=schema_data['repo_enable_downloads'],
932 )
932 )
933
933
934 if schema_data['repo_fork_of']:
934 if schema_data['repo_fork_of']:
935 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
935 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
936 validated_updates['fork_id'] = fork_repo.repo_id
936 validated_updates['fork_id'] = fork_repo.repo_id
937
937
938 # extra fields
938 # extra fields
939 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
939 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
940 if fields:
940 if fields:
941 validated_updates.update(fields)
941 validated_updates.update(fields)
942
942
943 try:
943 try:
944 RepoModel().update(repo, **validated_updates)
944 RepoModel().update(repo, **validated_updates)
945 audit_logger.store_api(
945 audit_logger.store_api(
946 'repo.edit', action_data={'old_data': old_values},
946 'repo.edit', action_data={'old_data': old_values},
947 user=apiuser, repo=repo)
947 user=apiuser, repo=repo)
948 Session().commit()
948 Session().commit()
949 return {
949 return {
950 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
950 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
951 'repository': repo.get_api_data(include_secrets=include_secrets)
951 'repository': repo.get_api_data(include_secrets=include_secrets)
952 }
952 }
953 except Exception:
953 except Exception:
954 log.exception(
954 log.exception(
955 u"Exception while trying to update the repository %s",
955 u"Exception while trying to update the repository %s",
956 repoid)
956 repoid)
957 raise JSONRPCError('failed to update repo `%s`' % repoid)
957 raise JSONRPCError('failed to update repo `%s`' % repoid)
958
958
959
959
960 @jsonrpc_method()
960 @jsonrpc_method()
961 def fork_repo(request, apiuser, repoid, fork_name,
961 def fork_repo(request, apiuser, repoid, fork_name,
962 owner=Optional(OAttr('apiuser')),
962 owner=Optional(OAttr('apiuser')),
963 description=Optional(''),
963 description=Optional(''),
964 private=Optional(False),
964 private=Optional(False),
965 clone_uri=Optional(None),
965 clone_uri=Optional(None),
966 landing_rev=Optional('rev:tip'),
966 landing_rev=Optional('rev:tip'),
967 copy_permissions=Optional(False)):
967 copy_permissions=Optional(False)):
968 """
968 """
969 Creates a fork of the specified |repo|.
969 Creates a fork of the specified |repo|.
970
970
971 * If the fork_name contains "/", fork will be created inside
971 * If the fork_name contains "/", fork will be created inside
972 a repository group or nested repository groups
972 a repository group or nested repository groups
973
973
974 For example "foo/bar/fork-repo" will create fork called "fork-repo"
974 For example "foo/bar/fork-repo" will create fork called "fork-repo"
975 inside group "foo/bar". You have to have permissions to access and
975 inside group "foo/bar". You have to have permissions to access and
976 write to the last repository group ("bar" in this example)
976 write to the last repository group ("bar" in this example)
977
977
978 This command can only be run using an |authtoken| with minimum
978 This command can only be run using an |authtoken| with minimum
979 read permissions of the forked repo, create fork permissions for an user.
979 read permissions of the forked repo, create fork permissions for an user.
980
980
981 :param apiuser: This is filled automatically from the |authtoken|.
981 :param apiuser: This is filled automatically from the |authtoken|.
982 :type apiuser: AuthUser
982 :type apiuser: AuthUser
983 :param repoid: Set repository name or repository ID.
983 :param repoid: Set repository name or repository ID.
984 :type repoid: str or int
984 :type repoid: str or int
985 :param fork_name: Set the fork name, including it's repository group membership.
985 :param fork_name: Set the fork name, including it's repository group membership.
986 :type fork_name: str
986 :type fork_name: str
987 :param owner: Set the fork owner.
987 :param owner: Set the fork owner.
988 :type owner: str
988 :type owner: str
989 :param description: Set the fork description.
989 :param description: Set the fork description.
990 :type description: str
990 :type description: str
991 :param copy_permissions: Copy permissions from parent |repo|. The
991 :param copy_permissions: Copy permissions from parent |repo|. The
992 default is False.
992 default is False.
993 :type copy_permissions: bool
993 :type copy_permissions: bool
994 :param private: Make the fork private. The default is False.
994 :param private: Make the fork private. The default is False.
995 :type private: bool
995 :type private: bool
996 :param landing_rev: Set the landing revision. The default is tip.
996 :param landing_rev: Set the landing revision. The default is tip.
997
997
998 Example output:
998 Example output:
999
999
1000 .. code-block:: bash
1000 .. code-block:: bash
1001
1001
1002 id : <id_for_response>
1002 id : <id_for_response>
1003 api_key : "<api_key>"
1003 api_key : "<api_key>"
1004 args: {
1004 args: {
1005 "repoid" : "<reponame or repo_id>",
1005 "repoid" : "<reponame or repo_id>",
1006 "fork_name": "<forkname>",
1006 "fork_name": "<forkname>",
1007 "owner": "<username or user_id = Optional(=apiuser)>",
1007 "owner": "<username or user_id = Optional(=apiuser)>",
1008 "description": "<description>",
1008 "description": "<description>",
1009 "copy_permissions": "<bool>",
1009 "copy_permissions": "<bool>",
1010 "private": "<bool>",
1010 "private": "<bool>",
1011 "landing_rev": "<landing_rev>"
1011 "landing_rev": "<landing_rev>"
1012 }
1012 }
1013
1013
1014 Example error output:
1014 Example error output:
1015
1015
1016 .. code-block:: bash
1016 .. code-block:: bash
1017
1017
1018 id : <id_given_in_input>
1018 id : <id_given_in_input>
1019 result: {
1019 result: {
1020 "msg": "Created fork of `<reponame>` as `<forkname>`",
1020 "msg": "Created fork of `<reponame>` as `<forkname>`",
1021 "success": true,
1021 "success": true,
1022 "task": "<celery task id or None if done sync>"
1022 "task": "<celery task id or None if done sync>"
1023 }
1023 }
1024 error: null
1024 error: null
1025
1025
1026 """
1026 """
1027
1027
1028 repo = get_repo_or_error(repoid)
1028 repo = get_repo_or_error(repoid)
1029 repo_name = repo.repo_name
1029 repo_name = repo.repo_name
1030
1030
1031 if not has_superadmin_permission(apiuser):
1031 if not has_superadmin_permission(apiuser):
1032 # check if we have at least read permission for
1032 # check if we have at least read permission for
1033 # this repo that we fork !
1033 # this repo that we fork !
1034 _perms = (
1034 _perms = (
1035 'repository.admin', 'repository.write', 'repository.read')
1035 'repository.admin', 'repository.write', 'repository.read')
1036 validate_repo_permissions(apiuser, repoid, repo, _perms)
1036 validate_repo_permissions(apiuser, repoid, repo, _perms)
1037
1037
1038 # check if the regular user has at least fork permissions as well
1038 # check if the regular user has at least fork permissions as well
1039 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1039 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1040 raise JSONRPCForbidden()
1040 raise JSONRPCForbidden()
1041
1041
1042 # check if user can set owner parameter
1042 # check if user can set owner parameter
1043 owner = validate_set_owner_permissions(apiuser, owner)
1043 owner = validate_set_owner_permissions(apiuser, owner)
1044
1044
1045 description = Optional.extract(description)
1045 description = Optional.extract(description)
1046 copy_permissions = Optional.extract(copy_permissions)
1046 copy_permissions = Optional.extract(copy_permissions)
1047 clone_uri = Optional.extract(clone_uri)
1047 clone_uri = Optional.extract(clone_uri)
1048 landing_commit_ref = Optional.extract(landing_rev)
1048 landing_commit_ref = Optional.extract(landing_rev)
1049 private = Optional.extract(private)
1049 private = Optional.extract(private)
1050
1050
1051 schema = repo_schema.RepoSchema().bind(
1051 schema = repo_schema.RepoSchema().bind(
1052 repo_type_options=rhodecode.BACKENDS.keys(),
1052 repo_type_options=rhodecode.BACKENDS.keys(),
1053 # user caller
1053 # user caller
1054 user=apiuser)
1054 user=apiuser)
1055
1055
1056 try:
1056 try:
1057 schema_data = schema.deserialize(dict(
1057 schema_data = schema.deserialize(dict(
1058 repo_name=fork_name,
1058 repo_name=fork_name,
1059 repo_type=repo.repo_type,
1059 repo_type=repo.repo_type,
1060 repo_owner=owner.username,
1060 repo_owner=owner.username,
1061 repo_description=description,
1061 repo_description=description,
1062 repo_landing_commit_ref=landing_commit_ref,
1062 repo_landing_commit_ref=landing_commit_ref,
1063 repo_clone_uri=clone_uri,
1063 repo_clone_uri=clone_uri,
1064 repo_private=private,
1064 repo_private=private,
1065 repo_copy_permissions=copy_permissions))
1065 repo_copy_permissions=copy_permissions))
1066 except validation_schema.Invalid as err:
1066 except validation_schema.Invalid as err:
1067 raise JSONRPCValidationError(colander_exc=err)
1067 raise JSONRPCValidationError(colander_exc=err)
1068
1068
1069 try:
1069 try:
1070 data = {
1070 data = {
1071 'fork_parent_id': repo.repo_id,
1071 'fork_parent_id': repo.repo_id,
1072
1072
1073 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1073 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1074 'repo_name_full': schema_data['repo_name'],
1074 'repo_name_full': schema_data['repo_name'],
1075 'repo_group': schema_data['repo_group']['repo_group_id'],
1075 'repo_group': schema_data['repo_group']['repo_group_id'],
1076 'repo_type': schema_data['repo_type'],
1076 'repo_type': schema_data['repo_type'],
1077 'description': schema_data['repo_description'],
1077 'description': schema_data['repo_description'],
1078 'private': schema_data['repo_private'],
1078 'private': schema_data['repo_private'],
1079 'copy_permissions': schema_data['repo_copy_permissions'],
1079 'copy_permissions': schema_data['repo_copy_permissions'],
1080 'landing_rev': schema_data['repo_landing_commit_ref'],
1080 'landing_rev': schema_data['repo_landing_commit_ref'],
1081 }
1081 }
1082
1082
1083 task = RepoModel().create_fork(data, cur_user=owner)
1083 task = RepoModel().create_fork(data, cur_user=owner)
1084 # no commit, it's done in RepoModel, or async via celery
1084 # no commit, it's done in RepoModel, or async via celery
1085 task_id = get_task_id(task)
1085 task_id = get_task_id(task)
1086
1086
1087 return {
1087 return {
1088 'msg': 'Created fork of `%s` as `%s`' % (
1088 'msg': 'Created fork of `%s` as `%s`' % (
1089 repo.repo_name, schema_data['repo_name']),
1089 repo.repo_name, schema_data['repo_name']),
1090 'success': True, # cannot return the repo data here since fork
1090 'success': True, # cannot return the repo data here since fork
1091 # can be done async
1091 # can be done async
1092 'task': task_id
1092 'task': task_id
1093 }
1093 }
1094 except Exception:
1094 except Exception:
1095 log.exception(
1095 log.exception(
1096 u"Exception while trying to create fork %s",
1096 u"Exception while trying to create fork %s",
1097 schema_data['repo_name'])
1097 schema_data['repo_name'])
1098 raise JSONRPCError(
1098 raise JSONRPCError(
1099 'failed to fork repository `%s` as `%s`' % (
1099 'failed to fork repository `%s` as `%s`' % (
1100 repo_name, schema_data['repo_name']))
1100 repo_name, schema_data['repo_name']))
1101
1101
1102
1102
1103 @jsonrpc_method()
1103 @jsonrpc_method()
1104 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1104 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1105 """
1105 """
1106 Deletes a repository.
1106 Deletes a repository.
1107
1107
1108 * When the `forks` parameter is set it's possible to detach or delete
1108 * When the `forks` parameter is set it's possible to detach or delete
1109 forks of deleted repository.
1109 forks of deleted repository.
1110
1110
1111 This command can only be run using an |authtoken| with admin
1111 This command can only be run using an |authtoken| with admin
1112 permissions on the |repo|.
1112 permissions on the |repo|.
1113
1113
1114 :param apiuser: This is filled automatically from the |authtoken|.
1114 :param apiuser: This is filled automatically from the |authtoken|.
1115 :type apiuser: AuthUser
1115 :type apiuser: AuthUser
1116 :param repoid: Set the repository name or repository ID.
1116 :param repoid: Set the repository name or repository ID.
1117 :type repoid: str or int
1117 :type repoid: str or int
1118 :param forks: Set to `detach` or `delete` forks from the |repo|.
1118 :param forks: Set to `detach` or `delete` forks from the |repo|.
1119 :type forks: Optional(str)
1119 :type forks: Optional(str)
1120
1120
1121 Example error output:
1121 Example error output:
1122
1122
1123 .. code-block:: bash
1123 .. code-block:: bash
1124
1124
1125 id : <id_given_in_input>
1125 id : <id_given_in_input>
1126 result: {
1126 result: {
1127 "msg": "Deleted repository `<reponame>`",
1127 "msg": "Deleted repository `<reponame>`",
1128 "success": true
1128 "success": true
1129 }
1129 }
1130 error: null
1130 error: null
1131 """
1131 """
1132
1132
1133 repo = get_repo_or_error(repoid)
1133 repo = get_repo_or_error(repoid)
1134 repo_name = repo.repo_name
1134 repo_name = repo.repo_name
1135 if not has_superadmin_permission(apiuser):
1135 if not has_superadmin_permission(apiuser):
1136 _perms = ('repository.admin',)
1136 _perms = ('repository.admin',)
1137 validate_repo_permissions(apiuser, repoid, repo, _perms)
1137 validate_repo_permissions(apiuser, repoid, repo, _perms)
1138
1138
1139 try:
1139 try:
1140 handle_forks = Optional.extract(forks)
1140 handle_forks = Optional.extract(forks)
1141 _forks_msg = ''
1141 _forks_msg = ''
1142 _forks = [f for f in repo.forks]
1142 _forks = [f for f in repo.forks]
1143 if handle_forks == 'detach':
1143 if handle_forks == 'detach':
1144 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1144 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1145 elif handle_forks == 'delete':
1145 elif handle_forks == 'delete':
1146 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1146 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1147 elif _forks:
1147 elif _forks:
1148 raise JSONRPCError(
1148 raise JSONRPCError(
1149 'Cannot delete `%s` it still contains attached forks' %
1149 'Cannot delete `%s` it still contains attached forks' %
1150 (repo.repo_name,)
1150 (repo.repo_name,)
1151 )
1151 )
1152 old_data = repo.get_api_data()
1152 old_data = repo.get_api_data()
1153 RepoModel().delete(repo, forks=forks)
1153 RepoModel().delete(repo, forks=forks)
1154
1154
1155 repo = audit_logger.RepoWrap(repo_id=None,
1155 repo = audit_logger.RepoWrap(repo_id=None,
1156 repo_name=repo.repo_name)
1156 repo_name=repo.repo_name)
1157
1157
1158 audit_logger.store_api(
1158 audit_logger.store_api(
1159 'repo.delete', action_data={'old_data': old_data},
1159 'repo.delete', action_data={'old_data': old_data},
1160 user=apiuser, repo=repo)
1160 user=apiuser, repo=repo)
1161
1161
1162 ScmModel().mark_for_invalidation(repo_name, delete=True)
1162 ScmModel().mark_for_invalidation(repo_name, delete=True)
1163 Session().commit()
1163 Session().commit()
1164 return {
1164 return {
1165 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1165 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1166 'success': True
1166 'success': True
1167 }
1167 }
1168 except Exception:
1168 except Exception:
1169 log.exception("Exception occurred while trying to delete repo")
1169 log.exception("Exception occurred while trying to delete repo")
1170 raise JSONRPCError(
1170 raise JSONRPCError(
1171 'failed to delete repository `%s`' % (repo_name,)
1171 'failed to delete repository `%s`' % (repo_name,)
1172 )
1172 )
1173
1173
1174
1174
1175 #TODO: marcink, change name ?
1175 #TODO: marcink, change name ?
1176 @jsonrpc_method()
1176 @jsonrpc_method()
1177 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1177 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1178 """
1178 """
1179 Invalidates the cache for the specified repository.
1179 Invalidates the cache for the specified repository.
1180
1180
1181 This command can only be run using an |authtoken| with admin rights to
1181 This command can only be run using an |authtoken| with admin rights to
1182 the specified repository.
1182 the specified repository.
1183
1183
1184 This command takes the following options:
1184 This command takes the following options:
1185
1185
1186 :param apiuser: This is filled automatically from |authtoken|.
1186 :param apiuser: This is filled automatically from |authtoken|.
1187 :type apiuser: AuthUser
1187 :type apiuser: AuthUser
1188 :param repoid: Sets the repository name or repository ID.
1188 :param repoid: Sets the repository name or repository ID.
1189 :type repoid: str or int
1189 :type repoid: str or int
1190 :param delete_keys: This deletes the invalidated keys instead of
1190 :param delete_keys: This deletes the invalidated keys instead of
1191 just flagging them.
1191 just flagging them.
1192 :type delete_keys: Optional(``True`` | ``False``)
1192 :type delete_keys: Optional(``True`` | ``False``)
1193
1193
1194 Example output:
1194 Example output:
1195
1195
1196 .. code-block:: bash
1196 .. code-block:: bash
1197
1197
1198 id : <id_given_in_input>
1198 id : <id_given_in_input>
1199 result : {
1199 result : {
1200 'msg': Cache for repository `<repository name>` was invalidated,
1200 'msg': Cache for repository `<repository name>` was invalidated,
1201 'repository': <repository name>
1201 'repository': <repository name>
1202 }
1202 }
1203 error : null
1203 error : null
1204
1204
1205 Example error output:
1205 Example error output:
1206
1206
1207 .. code-block:: bash
1207 .. code-block:: bash
1208
1208
1209 id : <id_given_in_input>
1209 id : <id_given_in_input>
1210 result : null
1210 result : null
1211 error : {
1211 error : {
1212 'Error occurred during cache invalidation action'
1212 'Error occurred during cache invalidation action'
1213 }
1213 }
1214
1214
1215 """
1215 """
1216
1216
1217 repo = get_repo_or_error(repoid)
1217 repo = get_repo_or_error(repoid)
1218 if not has_superadmin_permission(apiuser):
1218 if not has_superadmin_permission(apiuser):
1219 _perms = ('repository.admin', 'repository.write',)
1219 _perms = ('repository.admin', 'repository.write',)
1220 validate_repo_permissions(apiuser, repoid, repo, _perms)
1220 validate_repo_permissions(apiuser, repoid, repo, _perms)
1221
1221
1222 delete = Optional.extract(delete_keys)
1222 delete = Optional.extract(delete_keys)
1223 try:
1223 try:
1224 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1224 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1225 return {
1225 return {
1226 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1226 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1227 'repository': repo.repo_name
1227 'repository': repo.repo_name
1228 }
1228 }
1229 except Exception:
1229 except Exception:
1230 log.exception(
1230 log.exception(
1231 "Exception occurred while trying to invalidate repo cache")
1231 "Exception occurred while trying to invalidate repo cache")
1232 raise JSONRPCError(
1232 raise JSONRPCError(
1233 'Error occurred during cache invalidation action'
1233 'Error occurred during cache invalidation action'
1234 )
1234 )
1235
1235
1236
1236
1237 #TODO: marcink, change name ?
1237 #TODO: marcink, change name ?
1238 @jsonrpc_method()
1238 @jsonrpc_method()
1239 def lock(request, apiuser, repoid, locked=Optional(None),
1239 def lock(request, apiuser, repoid, locked=Optional(None),
1240 userid=Optional(OAttr('apiuser'))):
1240 userid=Optional(OAttr('apiuser'))):
1241 """
1241 """
1242 Sets the lock state of the specified |repo| by the given user.
1242 Sets the lock state of the specified |repo| by the given user.
1243 From more information, see :ref:`repo-locking`.
1243 From more information, see :ref:`repo-locking`.
1244
1244
1245 * If the ``userid`` option is not set, the repository is locked to the
1245 * If the ``userid`` option is not set, the repository is locked to the
1246 user who called the method.
1246 user who called the method.
1247 * If the ``locked`` parameter is not set, the current lock state of the
1247 * If the ``locked`` parameter is not set, the current lock state of the
1248 repository is displayed.
1248 repository is displayed.
1249
1249
1250 This command can only be run using an |authtoken| with admin rights to
1250 This command can only be run using an |authtoken| with admin rights to
1251 the specified repository.
1251 the specified repository.
1252
1252
1253 This command takes the following options:
1253 This command takes the following options:
1254
1254
1255 :param apiuser: This is filled automatically from the |authtoken|.
1255 :param apiuser: This is filled automatically from the |authtoken|.
1256 :type apiuser: AuthUser
1256 :type apiuser: AuthUser
1257 :param repoid: Sets the repository name or repository ID.
1257 :param repoid: Sets the repository name or repository ID.
1258 :type repoid: str or int
1258 :type repoid: str or int
1259 :param locked: Sets the lock state.
1259 :param locked: Sets the lock state.
1260 :type locked: Optional(``True`` | ``False``)
1260 :type locked: Optional(``True`` | ``False``)
1261 :param userid: Set the repository lock to this user.
1261 :param userid: Set the repository lock to this user.
1262 :type userid: Optional(str or int)
1262 :type userid: Optional(str or int)
1263
1263
1264 Example error output:
1264 Example error output:
1265
1265
1266 .. code-block:: bash
1266 .. code-block:: bash
1267
1267
1268 id : <id_given_in_input>
1268 id : <id_given_in_input>
1269 result : {
1269 result : {
1270 'repo': '<reponame>',
1270 'repo': '<reponame>',
1271 'locked': <bool: lock state>,
1271 'locked': <bool: lock state>,
1272 'locked_since': <int: lock timestamp>,
1272 'locked_since': <int: lock timestamp>,
1273 'locked_by': <username of person who made the lock>,
1273 'locked_by': <username of person who made the lock>,
1274 'lock_reason': <str: reason for locking>,
1274 'lock_reason': <str: reason for locking>,
1275 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1275 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1276 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1276 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1277 or
1277 or
1278 'msg': 'Repo `<repository name>` not locked.'
1278 'msg': 'Repo `<repository name>` not locked.'
1279 or
1279 or
1280 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1280 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1281 }
1281 }
1282 error : null
1282 error : null
1283
1283
1284 Example error output:
1284 Example error output:
1285
1285
1286 .. code-block:: bash
1286 .. code-block:: bash
1287
1287
1288 id : <id_given_in_input>
1288 id : <id_given_in_input>
1289 result : null
1289 result : null
1290 error : {
1290 error : {
1291 'Error occurred locking repository `<reponame>`'
1291 'Error occurred locking repository `<reponame>`'
1292 }
1292 }
1293 """
1293 """
1294
1294
1295 repo = get_repo_or_error(repoid)
1295 repo = get_repo_or_error(repoid)
1296 if not has_superadmin_permission(apiuser):
1296 if not has_superadmin_permission(apiuser):
1297 # check if we have at least write permission for this repo !
1297 # check if we have at least write permission for this repo !
1298 _perms = ('repository.admin', 'repository.write',)
1298 _perms = ('repository.admin', 'repository.write',)
1299 validate_repo_permissions(apiuser, repoid, repo, _perms)
1299 validate_repo_permissions(apiuser, repoid, repo, _perms)
1300
1300
1301 # make sure normal user does not pass someone else userid,
1301 # make sure normal user does not pass someone else userid,
1302 # he is not allowed to do that
1302 # he is not allowed to do that
1303 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1303 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1304 raise JSONRPCError('userid is not the same as your user')
1304 raise JSONRPCError('userid is not the same as your user')
1305
1305
1306 if isinstance(userid, Optional):
1306 if isinstance(userid, Optional):
1307 userid = apiuser.user_id
1307 userid = apiuser.user_id
1308
1308
1309 user = get_user_or_error(userid)
1309 user = get_user_or_error(userid)
1310
1310
1311 if isinstance(locked, Optional):
1311 if isinstance(locked, Optional):
1312 lockobj = repo.locked
1312 lockobj = repo.locked
1313
1313
1314 if lockobj[0] is None:
1314 if lockobj[0] is None:
1315 _d = {
1315 _d = {
1316 'repo': repo.repo_name,
1316 'repo': repo.repo_name,
1317 'locked': False,
1317 'locked': False,
1318 'locked_since': None,
1318 'locked_since': None,
1319 'locked_by': None,
1319 'locked_by': None,
1320 'lock_reason': None,
1320 'lock_reason': None,
1321 'lock_state_changed': False,
1321 'lock_state_changed': False,
1322 'msg': 'Repo `%s` not locked.' % repo.repo_name
1322 'msg': 'Repo `%s` not locked.' % repo.repo_name
1323 }
1323 }
1324 return _d
1324 return _d
1325 else:
1325 else:
1326 _user_id, _time, _reason = lockobj
1326 _user_id, _time, _reason = lockobj
1327 lock_user = get_user_or_error(userid)
1327 lock_user = get_user_or_error(userid)
1328 _d = {
1328 _d = {
1329 'repo': repo.repo_name,
1329 'repo': repo.repo_name,
1330 'locked': True,
1330 'locked': True,
1331 'locked_since': _time,
1331 'locked_since': _time,
1332 'locked_by': lock_user.username,
1332 'locked_by': lock_user.username,
1333 'lock_reason': _reason,
1333 'lock_reason': _reason,
1334 'lock_state_changed': False,
1334 'lock_state_changed': False,
1335 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1335 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1336 % (repo.repo_name, lock_user.username,
1336 % (repo.repo_name, lock_user.username,
1337 json.dumps(time_to_datetime(_time))))
1337 json.dumps(time_to_datetime(_time))))
1338 }
1338 }
1339 return _d
1339 return _d
1340
1340
1341 # force locked state through a flag
1341 # force locked state through a flag
1342 else:
1342 else:
1343 locked = str2bool(locked)
1343 locked = str2bool(locked)
1344 lock_reason = Repository.LOCK_API
1344 lock_reason = Repository.LOCK_API
1345 try:
1345 try:
1346 if locked:
1346 if locked:
1347 lock_time = time.time()
1347 lock_time = time.time()
1348 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1348 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1349 else:
1349 else:
1350 lock_time = None
1350 lock_time = None
1351 Repository.unlock(repo)
1351 Repository.unlock(repo)
1352 _d = {
1352 _d = {
1353 'repo': repo.repo_name,
1353 'repo': repo.repo_name,
1354 'locked': locked,
1354 'locked': locked,
1355 'locked_since': lock_time,
1355 'locked_since': lock_time,
1356 'locked_by': user.username,
1356 'locked_by': user.username,
1357 'lock_reason': lock_reason,
1357 'lock_reason': lock_reason,
1358 'lock_state_changed': True,
1358 'lock_state_changed': True,
1359 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1359 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1360 % (user.username, repo.repo_name, locked))
1360 % (user.username, repo.repo_name, locked))
1361 }
1361 }
1362 return _d
1362 return _d
1363 except Exception:
1363 except Exception:
1364 log.exception(
1364 log.exception(
1365 "Exception occurred while trying to lock repository")
1365 "Exception occurred while trying to lock repository")
1366 raise JSONRPCError(
1366 raise JSONRPCError(
1367 'Error occurred locking repository `%s`' % repo.repo_name
1367 'Error occurred locking repository `%s`' % repo.repo_name
1368 )
1368 )
1369
1369
1370
1370
1371 @jsonrpc_method()
1371 @jsonrpc_method()
1372 def comment_commit(
1372 def comment_commit(
1373 request, apiuser, repoid, commit_id, message, status=Optional(None),
1373 request, apiuser, repoid, commit_id, message, status=Optional(None),
1374 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1374 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1375 resolves_comment_id=Optional(None),
1375 resolves_comment_id=Optional(None),
1376 userid=Optional(OAttr('apiuser'))):
1376 userid=Optional(OAttr('apiuser'))):
1377 """
1377 """
1378 Set a commit comment, and optionally change the status of the commit.
1378 Set a commit comment, and optionally change the status of the commit.
1379
1379
1380 :param apiuser: This is filled automatically from the |authtoken|.
1380 :param apiuser: This is filled automatically from the |authtoken|.
1381 :type apiuser: AuthUser
1381 :type apiuser: AuthUser
1382 :param repoid: Set the repository name or repository ID.
1382 :param repoid: Set the repository name or repository ID.
1383 :type repoid: str or int
1383 :type repoid: str or int
1384 :param commit_id: Specify the commit_id for which to set a comment.
1384 :param commit_id: Specify the commit_id for which to set a comment.
1385 :type commit_id: str
1385 :type commit_id: str
1386 :param message: The comment text.
1386 :param message: The comment text.
1387 :type message: str
1387 :type message: str
1388 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1388 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1389 'approved', 'rejected', 'under_review'
1389 'approved', 'rejected', 'under_review'
1390 :type status: str
1390 :type status: str
1391 :param comment_type: Comment type, one of: 'note', 'todo'
1391 :param comment_type: Comment type, one of: 'note', 'todo'
1392 :type comment_type: Optional(str), default: 'note'
1392 :type comment_type: Optional(str), default: 'note'
1393 :param userid: Set the user name of the comment creator.
1393 :param userid: Set the user name of the comment creator.
1394 :type userid: Optional(str or int)
1394 :type userid: Optional(str or int)
1395
1395
1396 Example error output:
1396 Example error output:
1397
1397
1398 .. code-block:: bash
1398 .. code-block:: bash
1399
1399
1400 {
1400 {
1401 "id" : <id_given_in_input>,
1401 "id" : <id_given_in_input>,
1402 "result" : {
1402 "result" : {
1403 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1403 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1404 "status_change": null or <status>,
1404 "status_change": null or <status>,
1405 "success": true
1405 "success": true
1406 },
1406 },
1407 "error" : null
1407 "error" : null
1408 }
1408 }
1409
1409
1410 """
1410 """
1411 repo = get_repo_or_error(repoid)
1411 repo = get_repo_or_error(repoid)
1412 if not has_superadmin_permission(apiuser):
1412 if not has_superadmin_permission(apiuser):
1413 _perms = ('repository.read', 'repository.write', 'repository.admin')
1413 _perms = ('repository.read', 'repository.write', 'repository.admin')
1414 validate_repo_permissions(apiuser, repoid, repo, _perms)
1414 validate_repo_permissions(apiuser, repoid, repo, _perms)
1415
1415
1416 try:
1416 try:
1417 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1417 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1418 except Exception as e:
1418 except Exception as e:
1419 log.exception('Failed to fetch commit')
1419 log.exception('Failed to fetch commit')
1420 raise JSONRPCError(e.message)
1420 raise JSONRPCError(e.message)
1421
1421
1422 if isinstance(userid, Optional):
1422 if isinstance(userid, Optional):
1423 userid = apiuser.user_id
1423 userid = apiuser.user_id
1424
1424
1425 user = get_user_or_error(userid)
1425 user = get_user_or_error(userid)
1426 status = Optional.extract(status)
1426 status = Optional.extract(status)
1427 comment_type = Optional.extract(comment_type)
1427 comment_type = Optional.extract(comment_type)
1428 resolves_comment_id = Optional.extract(resolves_comment_id)
1428 resolves_comment_id = Optional.extract(resolves_comment_id)
1429
1429
1430 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1430 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1431 if status and status not in allowed_statuses:
1431 if status and status not in allowed_statuses:
1432 raise JSONRPCError('Bad status, must be on '
1432 raise JSONRPCError('Bad status, must be on '
1433 'of %s got %s' % (allowed_statuses, status,))
1433 'of %s got %s' % (allowed_statuses, status,))
1434
1434
1435 if resolves_comment_id:
1435 if resolves_comment_id:
1436 comment = ChangesetComment.get(resolves_comment_id)
1436 comment = ChangesetComment.get(resolves_comment_id)
1437 if not comment:
1437 if not comment:
1438 raise JSONRPCError(
1438 raise JSONRPCError(
1439 'Invalid resolves_comment_id `%s` for this commit.'
1439 'Invalid resolves_comment_id `%s` for this commit.'
1440 % resolves_comment_id)
1440 % resolves_comment_id)
1441 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1441 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1442 raise JSONRPCError(
1442 raise JSONRPCError(
1443 'Comment `%s` is wrong type for setting status to resolved.'
1443 'Comment `%s` is wrong type for setting status to resolved.'
1444 % resolves_comment_id)
1444 % resolves_comment_id)
1445
1445
1446 try:
1446 try:
1447 rc_config = SettingsModel().get_all_settings()
1447 rc_config = SettingsModel().get_all_settings()
1448 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1448 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1449 status_change_label = ChangesetStatus.get_status_lbl(status)
1449 status_change_label = ChangesetStatus.get_status_lbl(status)
1450 comment = CommentsModel().create(
1450 comment = CommentsModel().create(
1451 message, repo, user, commit_id=commit_id,
1451 message, repo, user, commit_id=commit_id,
1452 status_change=status_change_label,
1452 status_change=status_change_label,
1453 status_change_type=status,
1453 status_change_type=status,
1454 renderer=renderer,
1454 renderer=renderer,
1455 comment_type=comment_type,
1455 comment_type=comment_type,
1456 resolves_comment_id=resolves_comment_id
1456 resolves_comment_id=resolves_comment_id
1457 )
1457 )
1458 if status:
1458 if status:
1459 # also do a status change
1459 # also do a status change
1460 try:
1460 try:
1461 ChangesetStatusModel().set_status(
1461 ChangesetStatusModel().set_status(
1462 repo, status, user, comment, revision=commit_id,
1462 repo, status, user, comment, revision=commit_id,
1463 dont_allow_on_closed_pull_request=True
1463 dont_allow_on_closed_pull_request=True
1464 )
1464 )
1465 except StatusChangeOnClosedPullRequestError:
1465 except StatusChangeOnClosedPullRequestError:
1466 log.exception(
1466 log.exception(
1467 "Exception occurred while trying to change repo commit status")
1467 "Exception occurred while trying to change repo commit status")
1468 msg = ('Changing status on a changeset associated with '
1468 msg = ('Changing status on a changeset associated with '
1469 'a closed pull request is not allowed')
1469 'a closed pull request is not allowed')
1470 raise JSONRPCError(msg)
1470 raise JSONRPCError(msg)
1471
1471
1472 Session().commit()
1472 Session().commit()
1473 return {
1473 return {
1474 'msg': (
1474 'msg': (
1475 'Commented on commit `%s` for repository `%s`' % (
1475 'Commented on commit `%s` for repository `%s`' % (
1476 comment.revision, repo.repo_name)),
1476 comment.revision, repo.repo_name)),
1477 'status_change': status,
1477 'status_change': status,
1478 'success': True,
1478 'success': True,
1479 }
1479 }
1480 except JSONRPCError:
1480 except JSONRPCError:
1481 # catch any inside errors, and re-raise them to prevent from
1481 # catch any inside errors, and re-raise them to prevent from
1482 # below global catch to silence them
1482 # below global catch to silence them
1483 raise
1483 raise
1484 except Exception:
1484 except Exception:
1485 log.exception("Exception occurred while trying to comment on commit")
1485 log.exception("Exception occurred while trying to comment on commit")
1486 raise JSONRPCError(
1486 raise JSONRPCError(
1487 'failed to set comment on repository `%s`' % (repo.repo_name,)
1487 'failed to set comment on repository `%s`' % (repo.repo_name,)
1488 )
1488 )
1489
1489
1490
1490
1491 @jsonrpc_method()
1491 @jsonrpc_method()
1492 def grant_user_permission(request, apiuser, repoid, userid, perm):
1492 def grant_user_permission(request, apiuser, repoid, userid, perm):
1493 """
1493 """
1494 Grant permissions for the specified user on the given repository,
1494 Grant permissions for the specified user on the given repository,
1495 or update existing permissions if found.
1495 or update existing permissions if found.
1496
1496
1497 This command can only be run using an |authtoken| with admin
1497 This command can only be run using an |authtoken| with admin
1498 permissions on the |repo|.
1498 permissions on the |repo|.
1499
1499
1500 :param apiuser: This is filled automatically from the |authtoken|.
1500 :param apiuser: This is filled automatically from the |authtoken|.
1501 :type apiuser: AuthUser
1501 :type apiuser: AuthUser
1502 :param repoid: Set the repository name or repository ID.
1502 :param repoid: Set the repository name or repository ID.
1503 :type repoid: str or int
1503 :type repoid: str or int
1504 :param userid: Set the user name.
1504 :param userid: Set the user name.
1505 :type userid: str
1505 :type userid: str
1506 :param perm: Set the user permissions, using the following format
1506 :param perm: Set the user permissions, using the following format
1507 ``(repository.(none|read|write|admin))``
1507 ``(repository.(none|read|write|admin))``
1508 :type perm: str
1508 :type perm: str
1509
1509
1510 Example output:
1510 Example output:
1511
1511
1512 .. code-block:: bash
1512 .. code-block:: bash
1513
1513
1514 id : <id_given_in_input>
1514 id : <id_given_in_input>
1515 result: {
1515 result: {
1516 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1516 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1517 "success": true
1517 "success": true
1518 }
1518 }
1519 error: null
1519 error: null
1520 """
1520 """
1521
1521
1522 repo = get_repo_or_error(repoid)
1522 repo = get_repo_or_error(repoid)
1523 user = get_user_or_error(userid)
1523 user = get_user_or_error(userid)
1524 perm = get_perm_or_error(perm)
1524 perm = get_perm_or_error(perm)
1525 if not has_superadmin_permission(apiuser):
1525 if not has_superadmin_permission(apiuser):
1526 _perms = ('repository.admin',)
1526 _perms = ('repository.admin',)
1527 validate_repo_permissions(apiuser, repoid, repo, _perms)
1527 validate_repo_permissions(apiuser, repoid, repo, _perms)
1528
1528
1529 try:
1529 try:
1530
1530
1531 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1531 RepoModel().grant_user_permission(repo=repo, user=user, perm=perm)
1532
1532
1533 Session().commit()
1533 Session().commit()
1534 return {
1534 return {
1535 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1535 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1536 perm.permission_name, user.username, repo.repo_name
1536 perm.permission_name, user.username, repo.repo_name
1537 ),
1537 ),
1538 'success': True
1538 'success': True
1539 }
1539 }
1540 except Exception:
1540 except Exception:
1541 log.exception(
1541 log.exception(
1542 "Exception occurred while trying edit permissions for repo")
1542 "Exception occurred while trying edit permissions for repo")
1543 raise JSONRPCError(
1543 raise JSONRPCError(
1544 'failed to edit permission for user: `%s` in repo: `%s`' % (
1544 'failed to edit permission for user: `%s` in repo: `%s`' % (
1545 userid, repoid
1545 userid, repoid
1546 )
1546 )
1547 )
1547 )
1548
1548
1549
1549
1550 @jsonrpc_method()
1550 @jsonrpc_method()
1551 def revoke_user_permission(request, apiuser, repoid, userid):
1551 def revoke_user_permission(request, apiuser, repoid, userid):
1552 """
1552 """
1553 Revoke permission for a user on the specified repository.
1553 Revoke permission for a user on the specified repository.
1554
1554
1555 This command can only be run using an |authtoken| with admin
1555 This command can only be run using an |authtoken| with admin
1556 permissions on the |repo|.
1556 permissions on the |repo|.
1557
1557
1558 :param apiuser: This is filled automatically from the |authtoken|.
1558 :param apiuser: This is filled automatically from the |authtoken|.
1559 :type apiuser: AuthUser
1559 :type apiuser: AuthUser
1560 :param repoid: Set the repository name or repository ID.
1560 :param repoid: Set the repository name or repository ID.
1561 :type repoid: str or int
1561 :type repoid: str or int
1562 :param userid: Set the user name of revoked user.
1562 :param userid: Set the user name of revoked user.
1563 :type userid: str or int
1563 :type userid: str or int
1564
1564
1565 Example error output:
1565 Example error output:
1566
1566
1567 .. code-block:: bash
1567 .. code-block:: bash
1568
1568
1569 id : <id_given_in_input>
1569 id : <id_given_in_input>
1570 result: {
1570 result: {
1571 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1571 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1572 "success": true
1572 "success": true
1573 }
1573 }
1574 error: null
1574 error: null
1575 """
1575 """
1576
1576
1577 repo = get_repo_or_error(repoid)
1577 repo = get_repo_or_error(repoid)
1578 user = get_user_or_error(userid)
1578 user = get_user_or_error(userid)
1579 if not has_superadmin_permission(apiuser):
1579 if not has_superadmin_permission(apiuser):
1580 _perms = ('repository.admin',)
1580 _perms = ('repository.admin',)
1581 validate_repo_permissions(apiuser, repoid, repo, _perms)
1581 validate_repo_permissions(apiuser, repoid, repo, _perms)
1582
1582
1583 try:
1583 try:
1584 RepoModel().revoke_user_permission(repo=repo, user=user)
1584 RepoModel().revoke_user_permission(repo=repo, user=user)
1585 Session().commit()
1585 Session().commit()
1586 return {
1586 return {
1587 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1587 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1588 user.username, repo.repo_name
1588 user.username, repo.repo_name
1589 ),
1589 ),
1590 'success': True
1590 'success': True
1591 }
1591 }
1592 except Exception:
1592 except Exception:
1593 log.exception(
1593 log.exception(
1594 "Exception occurred while trying revoke permissions to repo")
1594 "Exception occurred while trying revoke permissions to repo")
1595 raise JSONRPCError(
1595 raise JSONRPCError(
1596 'failed to edit permission for user: `%s` in repo: `%s`' % (
1596 'failed to edit permission for user: `%s` in repo: `%s`' % (
1597 userid, repoid
1597 userid, repoid
1598 )
1598 )
1599 )
1599 )
1600
1600
1601
1601
1602 @jsonrpc_method()
1602 @jsonrpc_method()
1603 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1603 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1604 """
1604 """
1605 Grant permission for a user group on the specified repository,
1605 Grant permission for a user group on the specified repository,
1606 or update existing permissions.
1606 or update existing permissions.
1607
1607
1608 This command can only be run using an |authtoken| with admin
1608 This command can only be run using an |authtoken| with admin
1609 permissions on the |repo|.
1609 permissions on the |repo|.
1610
1610
1611 :param apiuser: This is filled automatically from the |authtoken|.
1611 :param apiuser: This is filled automatically from the |authtoken|.
1612 :type apiuser: AuthUser
1612 :type apiuser: AuthUser
1613 :param repoid: Set the repository name or repository ID.
1613 :param repoid: Set the repository name or repository ID.
1614 :type repoid: str or int
1614 :type repoid: str or int
1615 :param usergroupid: Specify the ID of the user group.
1615 :param usergroupid: Specify the ID of the user group.
1616 :type usergroupid: str or int
1616 :type usergroupid: str or int
1617 :param perm: Set the user group permissions using the following
1617 :param perm: Set the user group permissions using the following
1618 format: (repository.(none|read|write|admin))
1618 format: (repository.(none|read|write|admin))
1619 :type perm: str
1619 :type perm: str
1620
1620
1621 Example output:
1621 Example output:
1622
1622
1623 .. code-block:: bash
1623 .. code-block:: bash
1624
1624
1625 id : <id_given_in_input>
1625 id : <id_given_in_input>
1626 result : {
1626 result : {
1627 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1627 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1628 "success": true
1628 "success": true
1629
1629
1630 }
1630 }
1631 error : null
1631 error : null
1632
1632
1633 Example error output:
1633 Example error output:
1634
1634
1635 .. code-block:: bash
1635 .. code-block:: bash
1636
1636
1637 id : <id_given_in_input>
1637 id : <id_given_in_input>
1638 result : null
1638 result : null
1639 error : {
1639 error : {
1640 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1640 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1641 }
1641 }
1642
1642
1643 """
1643 """
1644
1644
1645 repo = get_repo_or_error(repoid)
1645 repo = get_repo_or_error(repoid)
1646 perm = get_perm_or_error(perm)
1646 perm = get_perm_or_error(perm)
1647 if not has_superadmin_permission(apiuser):
1647 if not has_superadmin_permission(apiuser):
1648 _perms = ('repository.admin',)
1648 _perms = ('repository.admin',)
1649 validate_repo_permissions(apiuser, repoid, repo, _perms)
1649 validate_repo_permissions(apiuser, repoid, repo, _perms)
1650
1650
1651 user_group = get_user_group_or_error(usergroupid)
1651 user_group = get_user_group_or_error(usergroupid)
1652 if not has_superadmin_permission(apiuser):
1652 if not has_superadmin_permission(apiuser):
1653 # check if we have at least read permission for this user group !
1653 # check if we have at least read permission for this user group !
1654 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1654 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1655 if not HasUserGroupPermissionAnyApi(*_perms)(
1655 if not HasUserGroupPermissionAnyApi(*_perms)(
1656 user=apiuser, user_group_name=user_group.users_group_name):
1656 user=apiuser, user_group_name=user_group.users_group_name):
1657 raise JSONRPCError(
1657 raise JSONRPCError(
1658 'user group `%s` does not exist' % (usergroupid,))
1658 'user group `%s` does not exist' % (usergroupid,))
1659
1659
1660 try:
1660 try:
1661 RepoModel().grant_user_group_permission(
1661 RepoModel().grant_user_group_permission(
1662 repo=repo, group_name=user_group, perm=perm)
1662 repo=repo, group_name=user_group, perm=perm)
1663
1663
1664 Session().commit()
1664 Session().commit()
1665 return {
1665 return {
1666 'msg': 'Granted perm: `%s` for user group: `%s` in '
1666 'msg': 'Granted perm: `%s` for user group: `%s` in '
1667 'repo: `%s`' % (
1667 'repo: `%s`' % (
1668 perm.permission_name, user_group.users_group_name,
1668 perm.permission_name, user_group.users_group_name,
1669 repo.repo_name
1669 repo.repo_name
1670 ),
1670 ),
1671 'success': True
1671 'success': True
1672 }
1672 }
1673 except Exception:
1673 except Exception:
1674 log.exception(
1674 log.exception(
1675 "Exception occurred while trying change permission on repo")
1675 "Exception occurred while trying change permission on repo")
1676 raise JSONRPCError(
1676 raise JSONRPCError(
1677 'failed to edit permission for user group: `%s` in '
1677 'failed to edit permission for user group: `%s` in '
1678 'repo: `%s`' % (
1678 'repo: `%s`' % (
1679 usergroupid, repo.repo_name
1679 usergroupid, repo.repo_name
1680 )
1680 )
1681 )
1681 )
1682
1682
1683
1683
1684 @jsonrpc_method()
1684 @jsonrpc_method()
1685 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1685 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1686 """
1686 """
1687 Revoke the permissions of a user group on a given repository.
1687 Revoke the permissions of a user group on a given repository.
1688
1688
1689 This command can only be run using an |authtoken| with admin
1689 This command can only be run using an |authtoken| with admin
1690 permissions on the |repo|.
1690 permissions on the |repo|.
1691
1691
1692 :param apiuser: This is filled automatically from the |authtoken|.
1692 :param apiuser: This is filled automatically from the |authtoken|.
1693 :type apiuser: AuthUser
1693 :type apiuser: AuthUser
1694 :param repoid: Set the repository name or repository ID.
1694 :param repoid: Set the repository name or repository ID.
1695 :type repoid: str or int
1695 :type repoid: str or int
1696 :param usergroupid: Specify the user group ID.
1696 :param usergroupid: Specify the user group ID.
1697 :type usergroupid: str or int
1697 :type usergroupid: str or int
1698
1698
1699 Example output:
1699 Example output:
1700
1700
1701 .. code-block:: bash
1701 .. code-block:: bash
1702
1702
1703 id : <id_given_in_input>
1703 id : <id_given_in_input>
1704 result: {
1704 result: {
1705 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1705 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1706 "success": true
1706 "success": true
1707 }
1707 }
1708 error: null
1708 error: null
1709 """
1709 """
1710
1710
1711 repo = get_repo_or_error(repoid)
1711 repo = get_repo_or_error(repoid)
1712 if not has_superadmin_permission(apiuser):
1712 if not has_superadmin_permission(apiuser):
1713 _perms = ('repository.admin',)
1713 _perms = ('repository.admin',)
1714 validate_repo_permissions(apiuser, repoid, repo, _perms)
1714 validate_repo_permissions(apiuser, repoid, repo, _perms)
1715
1715
1716 user_group = get_user_group_or_error(usergroupid)
1716 user_group = get_user_group_or_error(usergroupid)
1717 if not has_superadmin_permission(apiuser):
1717 if not has_superadmin_permission(apiuser):
1718 # check if we have at least read permission for this user group !
1718 # check if we have at least read permission for this user group !
1719 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1719 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1720 if not HasUserGroupPermissionAnyApi(*_perms)(
1720 if not HasUserGroupPermissionAnyApi(*_perms)(
1721 user=apiuser, user_group_name=user_group.users_group_name):
1721 user=apiuser, user_group_name=user_group.users_group_name):
1722 raise JSONRPCError(
1722 raise JSONRPCError(
1723 'user group `%s` does not exist' % (usergroupid,))
1723 'user group `%s` does not exist' % (usergroupid,))
1724
1724
1725 try:
1725 try:
1726 RepoModel().revoke_user_group_permission(
1726 RepoModel().revoke_user_group_permission(
1727 repo=repo, group_name=user_group)
1727 repo=repo, group_name=user_group)
1728
1728
1729 Session().commit()
1729 Session().commit()
1730 return {
1730 return {
1731 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1731 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1732 user_group.users_group_name, repo.repo_name
1732 user_group.users_group_name, repo.repo_name
1733 ),
1733 ),
1734 'success': True
1734 'success': True
1735 }
1735 }
1736 except Exception:
1736 except Exception:
1737 log.exception("Exception occurred while trying revoke "
1737 log.exception("Exception occurred while trying revoke "
1738 "user group permission on repo")
1738 "user group permission on repo")
1739 raise JSONRPCError(
1739 raise JSONRPCError(
1740 'failed to edit permission for user group: `%s` in '
1740 'failed to edit permission for user group: `%s` in '
1741 'repo: `%s`' % (
1741 'repo: `%s`' % (
1742 user_group.users_group_name, repo.repo_name
1742 user_group.users_group_name, repo.repo_name
1743 )
1743 )
1744 )
1744 )
1745
1745
1746
1746
1747 @jsonrpc_method()
1747 @jsonrpc_method()
1748 def pull(request, apiuser, repoid):
1748 def pull(request, apiuser, repoid):
1749 """
1749 """
1750 Triggers a pull on the given repository from a remote location. You
1750 Triggers a pull on the given repository from a remote location. You
1751 can use this to keep remote repositories up-to-date.
1751 can use this to keep remote repositories up-to-date.
1752
1752
1753 This command can only be run using an |authtoken| with admin
1753 This command can only be run using an |authtoken| with admin
1754 rights to the specified repository. For more information,
1754 rights to the specified repository. For more information,
1755 see :ref:`config-token-ref`.
1755 see :ref:`config-token-ref`.
1756
1756
1757 This command takes the following options:
1757 This command takes the following options:
1758
1758
1759 :param apiuser: This is filled automatically from the |authtoken|.
1759 :param apiuser: This is filled automatically from the |authtoken|.
1760 :type apiuser: AuthUser
1760 :type apiuser: AuthUser
1761 :param repoid: The repository name or repository ID.
1761 :param repoid: The repository name or repository ID.
1762 :type repoid: str or int
1762 :type repoid: str or int
1763
1763
1764 Example output:
1764 Example output:
1765
1765
1766 .. code-block:: bash
1766 .. code-block:: bash
1767
1767
1768 id : <id_given_in_input>
1768 id : <id_given_in_input>
1769 result : {
1769 result : {
1770 "msg": "Pulled from `<repository name>`"
1770 "msg": "Pulled from `<repository name>`"
1771 "repository": "<repository name>"
1771 "repository": "<repository name>"
1772 }
1772 }
1773 error : null
1773 error : null
1774
1774
1775 Example error output:
1775 Example error output:
1776
1776
1777 .. code-block:: bash
1777 .. code-block:: bash
1778
1778
1779 id : <id_given_in_input>
1779 id : <id_given_in_input>
1780 result : null
1780 result : null
1781 error : {
1781 error : {
1782 "Unable to pull changes from `<reponame>`"
1782 "Unable to pull changes from `<reponame>`"
1783 }
1783 }
1784
1784
1785 """
1785 """
1786
1786
1787 repo = get_repo_or_error(repoid)
1787 repo = get_repo_or_error(repoid)
1788 if not has_superadmin_permission(apiuser):
1788 if not has_superadmin_permission(apiuser):
1789 _perms = ('repository.admin',)
1789 _perms = ('repository.admin',)
1790 validate_repo_permissions(apiuser, repoid, repo, _perms)
1790 validate_repo_permissions(apiuser, repoid, repo, _perms)
1791
1791
1792 try:
1792 try:
1793 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1793 ScmModel().pull_changes(repo.repo_name, apiuser.username)
1794 return {
1794 return {
1795 'msg': 'Pulled from `%s`' % repo.repo_name,
1795 'msg': 'Pulled from `%s`' % repo.repo_name,
1796 'repository': repo.repo_name
1796 'repository': repo.repo_name
1797 }
1797 }
1798 except Exception:
1798 except Exception:
1799 log.exception("Exception occurred while trying to "
1799 log.exception("Exception occurred while trying to "
1800 "pull changes from remote location")
1800 "pull changes from remote location")
1801 raise JSONRPCError(
1801 raise JSONRPCError(
1802 'Unable to pull changes from `%s`' % repo.repo_name
1802 'Unable to pull changes from `%s`' % repo.repo_name
1803 )
1803 )
1804
1804
1805
1805
1806 @jsonrpc_method()
1806 @jsonrpc_method()
1807 def strip(request, apiuser, repoid, revision, branch):
1807 def strip(request, apiuser, repoid, revision, branch):
1808 """
1808 """
1809 Strips the given revision from the specified repository.
1809 Strips the given revision from the specified repository.
1810
1810
1811 * This will remove the revision and all of its decendants.
1811 * This will remove the revision and all of its decendants.
1812
1812
1813 This command can only be run using an |authtoken| with admin rights to
1813 This command can only be run using an |authtoken| with admin rights to
1814 the specified repository.
1814 the specified repository.
1815
1815
1816 This command takes the following options:
1816 This command takes the following options:
1817
1817
1818 :param apiuser: This is filled automatically from the |authtoken|.
1818 :param apiuser: This is filled automatically from the |authtoken|.
1819 :type apiuser: AuthUser
1819 :type apiuser: AuthUser
1820 :param repoid: The repository name or repository ID.
1820 :param repoid: The repository name or repository ID.
1821 :type repoid: str or int
1821 :type repoid: str or int
1822 :param revision: The revision you wish to strip.
1822 :param revision: The revision you wish to strip.
1823 :type revision: str
1823 :type revision: str
1824 :param branch: The branch from which to strip the revision.
1824 :param branch: The branch from which to strip the revision.
1825 :type branch: str
1825 :type branch: str
1826
1826
1827 Example output:
1827 Example output:
1828
1828
1829 .. code-block:: bash
1829 .. code-block:: bash
1830
1830
1831 id : <id_given_in_input>
1831 id : <id_given_in_input>
1832 result : {
1832 result : {
1833 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1833 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1834 "repository": "<repository name>"
1834 "repository": "<repository name>"
1835 }
1835 }
1836 error : null
1836 error : null
1837
1837
1838 Example error output:
1838 Example error output:
1839
1839
1840 .. code-block:: bash
1840 .. code-block:: bash
1841
1841
1842 id : <id_given_in_input>
1842 id : <id_given_in_input>
1843 result : null
1843 result : null
1844 error : {
1844 error : {
1845 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1845 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1846 }
1846 }
1847
1847
1848 """
1848 """
1849
1849
1850 repo = get_repo_or_error(repoid)
1850 repo = get_repo_or_error(repoid)
1851 if not has_superadmin_permission(apiuser):
1851 if not has_superadmin_permission(apiuser):
1852 _perms = ('repository.admin',)
1852 _perms = ('repository.admin',)
1853 validate_repo_permissions(apiuser, repoid, repo, _perms)
1853 validate_repo_permissions(apiuser, repoid, repo, _perms)
1854
1854
1855 try:
1855 try:
1856 ScmModel().strip(repo, revision, branch)
1856 ScmModel().strip(repo, revision, branch)
1857 audit_logger.store_api(
1857 audit_logger.store_api(
1858 'repo.commit.strip', action_data={'commit_id': revision},
1858 'repo.commit.strip', action_data={'commit_id': revision},
1859 repo=repo,
1859 repo=repo,
1860 user=apiuser, commit=True)
1860 user=apiuser, commit=True)
1861
1861
1862 return {
1862 return {
1863 'msg': 'Stripped commit %s from repo `%s`' % (
1863 'msg': 'Stripped commit %s from repo `%s`' % (
1864 revision, repo.repo_name),
1864 revision, repo.repo_name),
1865 'repository': repo.repo_name
1865 'repository': repo.repo_name
1866 }
1866 }
1867 except Exception:
1867 except Exception:
1868 log.exception("Exception while trying to strip")
1868 log.exception("Exception while trying to strip")
1869 raise JSONRPCError(
1869 raise JSONRPCError(
1870 'Unable to strip commit %s from repo `%s`' % (
1870 'Unable to strip commit %s from repo `%s`' % (
1871 revision, repo.repo_name)
1871 revision, repo.repo_name)
1872 )
1872 )
1873
1873
1874
1874
1875 @jsonrpc_method()
1875 @jsonrpc_method()
1876 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1876 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1877 """
1877 """
1878 Returns all settings for a repository. If key is given it only returns the
1878 Returns all settings for a repository. If key is given it only returns the
1879 setting identified by the key or null.
1879 setting identified by the key or null.
1880
1880
1881 :param apiuser: This is filled automatically from the |authtoken|.
1881 :param apiuser: This is filled automatically from the |authtoken|.
1882 :type apiuser: AuthUser
1882 :type apiuser: AuthUser
1883 :param repoid: The repository name or repository id.
1883 :param repoid: The repository name or repository id.
1884 :type repoid: str or int
1884 :type repoid: str or int
1885 :param key: Key of the setting to return.
1885 :param key: Key of the setting to return.
1886 :type: key: Optional(str)
1886 :type: key: Optional(str)
1887
1887
1888 Example output:
1888 Example output:
1889
1889
1890 .. code-block:: bash
1890 .. code-block:: bash
1891
1891
1892 {
1892 {
1893 "error": null,
1893 "error": null,
1894 "id": 237,
1894 "id": 237,
1895 "result": {
1895 "result": {
1896 "extensions_largefiles": true,
1896 "extensions_largefiles": true,
1897 "extensions_evolve": true,
1897 "extensions_evolve": true,
1898 "hooks_changegroup_push_logger": true,
1898 "hooks_changegroup_push_logger": true,
1899 "hooks_changegroup_repo_size": false,
1899 "hooks_changegroup_repo_size": false,
1900 "hooks_outgoing_pull_logger": true,
1900 "hooks_outgoing_pull_logger": true,
1901 "phases_publish": "True",
1901 "phases_publish": "True",
1902 "rhodecode_hg_use_rebase_for_merging": true,
1902 "rhodecode_hg_use_rebase_for_merging": true,
1903 "rhodecode_pr_merge_enabled": true,
1903 "rhodecode_pr_merge_enabled": true,
1904 "rhodecode_use_outdated_comments": true
1904 "rhodecode_use_outdated_comments": true
1905 }
1905 }
1906 }
1906 }
1907 """
1907 """
1908
1908
1909 # Restrict access to this api method to admins only.
1909 # Restrict access to this api method to admins only.
1910 if not has_superadmin_permission(apiuser):
1910 if not has_superadmin_permission(apiuser):
1911 raise JSONRPCForbidden()
1911 raise JSONRPCForbidden()
1912
1912
1913 try:
1913 try:
1914 repo = get_repo_or_error(repoid)
1914 repo = get_repo_or_error(repoid)
1915 settings_model = VcsSettingsModel(repo=repo)
1915 settings_model = VcsSettingsModel(repo=repo)
1916 settings = settings_model.get_global_settings()
1916 settings = settings_model.get_global_settings()
1917 settings.update(settings_model.get_repo_settings())
1917 settings.update(settings_model.get_repo_settings())
1918
1918
1919 # If only a single setting is requested fetch it from all settings.
1919 # If only a single setting is requested fetch it from all settings.
1920 key = Optional.extract(key)
1920 key = Optional.extract(key)
1921 if key is not None:
1921 if key is not None:
1922 settings = settings.get(key, None)
1922 settings = settings.get(key, None)
1923 except Exception:
1923 except Exception:
1924 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1924 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1925 log.exception(msg)
1925 log.exception(msg)
1926 raise JSONRPCError(msg)
1926 raise JSONRPCError(msg)
1927
1927
1928 return settings
1928 return settings
1929
1929
1930
1930
1931 @jsonrpc_method()
1931 @jsonrpc_method()
1932 def set_repo_settings(request, apiuser, repoid, settings):
1932 def set_repo_settings(request, apiuser, repoid, settings):
1933 """
1933 """
1934 Update repository settings. Returns true on success.
1934 Update repository settings. Returns true on success.
1935
1935
1936 :param apiuser: This is filled automatically from the |authtoken|.
1936 :param apiuser: This is filled automatically from the |authtoken|.
1937 :type apiuser: AuthUser
1937 :type apiuser: AuthUser
1938 :param repoid: The repository name or repository id.
1938 :param repoid: The repository name or repository id.
1939 :type repoid: str or int
1939 :type repoid: str or int
1940 :param settings: The new settings for the repository.
1940 :param settings: The new settings for the repository.
1941 :type: settings: dict
1941 :type: settings: dict
1942
1942
1943 Example output:
1943 Example output:
1944
1944
1945 .. code-block:: bash
1945 .. code-block:: bash
1946
1946
1947 {
1947 {
1948 "error": null,
1948 "error": null,
1949 "id": 237,
1949 "id": 237,
1950 "result": true
1950 "result": true
1951 }
1951 }
1952 """
1952 """
1953 # Restrict access to this api method to admins only.
1953 # Restrict access to this api method to admins only.
1954 if not has_superadmin_permission(apiuser):
1954 if not has_superadmin_permission(apiuser):
1955 raise JSONRPCForbidden()
1955 raise JSONRPCForbidden()
1956
1956
1957 if type(settings) is not dict:
1957 if type(settings) is not dict:
1958 raise JSONRPCError('Settings have to be a JSON Object.')
1958 raise JSONRPCError('Settings have to be a JSON Object.')
1959
1959
1960 try:
1960 try:
1961 settings_model = VcsSettingsModel(repo=repoid)
1961 settings_model = VcsSettingsModel(repo=repoid)
1962
1962
1963 # Merge global, repo and incoming settings.
1963 # Merge global, repo and incoming settings.
1964 new_settings = settings_model.get_global_settings()
1964 new_settings = settings_model.get_global_settings()
1965 new_settings.update(settings_model.get_repo_settings())
1965 new_settings.update(settings_model.get_repo_settings())
1966 new_settings.update(settings)
1966 new_settings.update(settings)
1967
1967
1968 # Update the settings.
1968 # Update the settings.
1969 inherit_global_settings = new_settings.get(
1969 inherit_global_settings = new_settings.get(
1970 'inherit_global_settings', False)
1970 'inherit_global_settings', False)
1971 settings_model.create_or_update_repo_settings(
1971 settings_model.create_or_update_repo_settings(
1972 new_settings, inherit_global_settings=inherit_global_settings)
1972 new_settings, inherit_global_settings=inherit_global_settings)
1973 Session().commit()
1973 Session().commit()
1974 except Exception:
1974 except Exception:
1975 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1975 msg = 'Failed to update settings for repository `{}`'.format(repoid)
1976 log.exception(msg)
1976 log.exception(msg)
1977 raise JSONRPCError(msg)
1977 raise JSONRPCError(msg)
1978
1978
1979 # Indicate success.
1979 # Indicate success.
1980 return True
1980 return True
1981
1981
1982
1982
1983 @jsonrpc_method()
1983 @jsonrpc_method()
1984 def maintenance(request, apiuser, repoid):
1984 def maintenance(request, apiuser, repoid):
1985 """
1985 """
1986 Triggers a maintenance on the given repository.
1986 Triggers a maintenance on the given repository.
1987
1987
1988 This command can only be run using an |authtoken| with admin
1988 This command can only be run using an |authtoken| with admin
1989 rights to the specified repository. For more information,
1989 rights to the specified repository. For more information,
1990 see :ref:`config-token-ref`.
1990 see :ref:`config-token-ref`.
1991
1991
1992 This command takes the following options:
1992 This command takes the following options:
1993
1993
1994 :param apiuser: This is filled automatically from the |authtoken|.
1994 :param apiuser: This is filled automatically from the |authtoken|.
1995 :type apiuser: AuthUser
1995 :type apiuser: AuthUser
1996 :param repoid: The repository name or repository ID.
1996 :param repoid: The repository name or repository ID.
1997 :type repoid: str or int
1997 :type repoid: str or int
1998
1998
1999 Example output:
1999 Example output:
2000
2000
2001 .. code-block:: bash
2001 .. code-block:: bash
2002
2002
2003 id : <id_given_in_input>
2003 id : <id_given_in_input>
2004 result : {
2004 result : {
2005 "msg": "executed maintenance command",
2005 "msg": "executed maintenance command",
2006 "executed_actions": [
2006 "executed_actions": [
2007 <action_message>, <action_message2>...
2007 <action_message>, <action_message2>...
2008 ],
2008 ],
2009 "repository": "<repository name>"
2009 "repository": "<repository name>"
2010 }
2010 }
2011 error : null
2011 error : null
2012
2012
2013 Example error output:
2013 Example error output:
2014
2014
2015 .. code-block:: bash
2015 .. code-block:: bash
2016
2016
2017 id : <id_given_in_input>
2017 id : <id_given_in_input>
2018 result : null
2018 result : null
2019 error : {
2019 error : {
2020 "Unable to execute maintenance on `<reponame>`"
2020 "Unable to execute maintenance on `<reponame>`"
2021 }
2021 }
2022
2022
2023 """
2023 """
2024
2024
2025 repo = get_repo_or_error(repoid)
2025 repo = get_repo_or_error(repoid)
2026 if not has_superadmin_permission(apiuser):
2026 if not has_superadmin_permission(apiuser):
2027 _perms = ('repository.admin',)
2027 _perms = ('repository.admin',)
2028 validate_repo_permissions(apiuser, repoid, repo, _perms)
2028 validate_repo_permissions(apiuser, repoid, repo, _perms)
2029
2029
2030 try:
2030 try:
2031 maintenance = repo_maintenance.RepoMaintenance()
2031 maintenance = repo_maintenance.RepoMaintenance()
2032 executed_actions = maintenance.execute(repo)
2032 executed_actions = maintenance.execute(repo)
2033
2033
2034 return {
2034 return {
2035 'msg': 'executed maintenance command',
2035 'msg': 'executed maintenance command',
2036 'executed_actions': executed_actions,
2036 'executed_actions': executed_actions,
2037 'repository': repo.repo_name
2037 'repository': repo.repo_name
2038 }
2038 }
2039 except Exception:
2039 except Exception:
2040 log.exception("Exception occurred while trying to run maintenance")
2040 log.exception("Exception occurred while trying to run maintenance")
2041 raise JSONRPCError(
2041 raise JSONRPCError(
2042 'Unable to execute maintenance on `%s`' % repo.repo_name)
2042 'Unable to execute maintenance on `%s`' % repo.repo_name)
@@ -1,719 +1,719 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2017 RhodeCode GmbH
3 # Copyright (C) 2011-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import JSONRPCValidationError
24 from rhodecode.api import JSONRPCValidationError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.utils import (
26 from rhodecode.api.utils import (
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
27 has_superadmin_permission, Optional, OAttr, get_user_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
28 get_repo_group_or_error, get_perm_or_error, get_user_group_or_error,
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
29 get_origin, validate_repo_group_permissions, validate_set_owner_permissions)
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import (
31 from rhodecode.lib.auth import (
32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
32 HasRepoGroupPermissionAnyApi, HasUserGroupPermissionAnyApi)
33 from rhodecode.model.db import Session
33 from rhodecode.model.db import Session
34 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.repo_group import RepoGroupModel
35 from rhodecode.model.scm import RepoGroupList
35 from rhodecode.model.scm import RepoGroupList
36 from rhodecode.model import validation_schema
36 from rhodecode.model import validation_schema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
37 from rhodecode.model.validation_schema.schemas import repo_group_schema
38
38
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 @jsonrpc_method()
43 @jsonrpc_method()
44 def get_repo_group(request, apiuser, repogroupid):
44 def get_repo_group(request, apiuser, repogroupid):
45 """
45 """
46 Return the specified |repo| group, along with permissions,
46 Return the specified |repo| group, along with permissions,
47 and repositories inside the group
47 and repositories inside the group
48
48
49 :param apiuser: This is filled automatically from the |authtoken|.
49 :param apiuser: This is filled automatically from the |authtoken|.
50 :type apiuser: AuthUser
50 :type apiuser: AuthUser
51 :param repogroupid: Specify the name of ID of the repository group.
51 :param repogroupid: Specify the name of ID of the repository group.
52 :type repogroupid: str or int
52 :type repogroupid: str or int
53
53
54
54
55 Example output:
55 Example output:
56
56
57 .. code-block:: bash
57 .. code-block:: bash
58
58
59 {
59 {
60 "error": null,
60 "error": null,
61 "id": repo-group-id,
61 "id": repo-group-id,
62 "result": {
62 "result": {
63 "group_description": "repo group description",
63 "group_description": "repo group description",
64 "group_id": 14,
64 "group_id": 14,
65 "group_name": "group name",
65 "group_name": "group name",
66 "permissions": [
66 "permissions": [
67 {
67 {
68 "name": "super-admin-username",
68 "name": "super-admin-username",
69 "origin": "super-admin",
69 "origin": "super-admin",
70 "permission": "group.admin",
70 "permission": "group.admin",
71 "type": "user"
71 "type": "user"
72 },
72 },
73 {
73 {
74 "name": "owner-name",
74 "name": "owner-name",
75 "origin": "owner",
75 "origin": "owner",
76 "permission": "group.admin",
76 "permission": "group.admin",
77 "type": "user"
77 "type": "user"
78 },
78 },
79 {
79 {
80 "name": "user-group-name",
80 "name": "user-group-name",
81 "origin": "permission",
81 "origin": "permission",
82 "permission": "group.write",
82 "permission": "group.write",
83 "type": "user_group"
83 "type": "user_group"
84 }
84 }
85 ],
85 ],
86 "owner": "owner-name",
86 "owner": "owner-name",
87 "parent_group": null,
87 "parent_group": null,
88 "repositories": [ repo-list ]
88 "repositories": [ repo-list ]
89 }
89 }
90 }
90 }
91 """
91 """
92
92
93 repo_group = get_repo_group_or_error(repogroupid)
93 repo_group = get_repo_group_or_error(repogroupid)
94 if not has_superadmin_permission(apiuser):
94 if not has_superadmin_permission(apiuser):
95 # check if we have at least read permission for this repo group !
95 # check if we have at least read permission for this repo group !
96 _perms = ('group.admin', 'group.write', 'group.read',)
96 _perms = ('group.admin', 'group.write', 'group.read',)
97 if not HasRepoGroupPermissionAnyApi(*_perms)(
97 if not HasRepoGroupPermissionAnyApi(*_perms)(
98 user=apiuser, group_name=repo_group.group_name):
98 user=apiuser, group_name=repo_group.group_name):
99 raise JSONRPCError(
99 raise JSONRPCError(
100 'repository group `%s` does not exist' % (repogroupid,))
100 'repository group `%s` does not exist' % (repogroupid,))
101
101
102 permissions = []
102 permissions = []
103 for _user in repo_group.permissions():
103 for _user in repo_group.permissions():
104 user_data = {
104 user_data = {
105 'name': _user.username,
105 'name': _user.username,
106 'permission': _user.permission,
106 'permission': _user.permission,
107 'origin': get_origin(_user),
107 'origin': get_origin(_user),
108 'type': "user",
108 'type': "user",
109 }
109 }
110 permissions.append(user_data)
110 permissions.append(user_data)
111
111
112 for _user_group in repo_group.permission_user_groups():
112 for _user_group in repo_group.permission_user_groups():
113 user_group_data = {
113 user_group_data = {
114 'name': _user_group.users_group_name,
114 'name': _user_group.users_group_name,
115 'permission': _user_group.permission,
115 'permission': _user_group.permission,
116 'origin': get_origin(_user_group),
116 'origin': get_origin(_user_group),
117 'type': "user_group",
117 'type': "user_group",
118 }
118 }
119 permissions.append(user_group_data)
119 permissions.append(user_group_data)
120
120
121 data = repo_group.get_api_data()
121 data = repo_group.get_api_data()
122 data["permissions"] = permissions
122 data["permissions"] = permissions
123 return data
123 return data
124
124
125
125
126 @jsonrpc_method()
126 @jsonrpc_method()
127 def get_repo_groups(request, apiuser):
127 def get_repo_groups(request, apiuser):
128 """
128 """
129 Returns all repository groups.
129 Returns all repository groups.
130
130
131 :param apiuser: This is filled automatically from the |authtoken|.
131 :param apiuser: This is filled automatically from the |authtoken|.
132 :type apiuser: AuthUser
132 :type apiuser: AuthUser
133 """
133 """
134
134
135 result = []
135 result = []
136 _perms = ('group.read', 'group.write', 'group.admin',)
136 _perms = ('group.read', 'group.write', 'group.admin',)
137 extras = {'user': apiuser}
137 extras = {'user': apiuser}
138 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
138 for repo_group in RepoGroupList(RepoGroupModel().get_all(),
139 perm_set=_perms, extra_kwargs=extras):
139 perm_set=_perms, extra_kwargs=extras):
140 result.append(repo_group.get_api_data())
140 result.append(repo_group.get_api_data())
141 return result
141 return result
142
142
143
143
144 @jsonrpc_method()
144 @jsonrpc_method()
145 def create_repo_group(
145 def create_repo_group(
146 request, apiuser, group_name,
146 request, apiuser, group_name,
147 owner=Optional(OAttr('apiuser')),
147 owner=Optional(OAttr('apiuser')),
148 description=Optional(''),
148 description=Optional(''),
149 copy_permissions=Optional(False)):
149 copy_permissions=Optional(False)):
150 """
150 """
151 Creates a repository group.
151 Creates a repository group.
152
152
153 * If the repository group name contains "/", repository group will be
153 * If the repository group name contains "/", repository group will be
154 created inside a repository group or nested repository groups
154 created inside a repository group or nested repository groups
155
155
156 For example "foo/bar/group1" will create repository group called "group1"
156 For example "foo/bar/group1" will create repository group called "group1"
157 inside group "foo/bar". You have to have permissions to access and
157 inside group "foo/bar". You have to have permissions to access and
158 write to the last repository group ("bar" in this example)
158 write to the last repository group ("bar" in this example)
159
159
160 This command can only be run using an |authtoken| with at least
160 This command can only be run using an |authtoken| with at least
161 permissions to create repository groups, or admin permissions to
161 permissions to create repository groups, or admin permissions to
162 parent repository groups.
162 parent repository groups.
163
163
164 :param apiuser: This is filled automatically from the |authtoken|.
164 :param apiuser: This is filled automatically from the |authtoken|.
165 :type apiuser: AuthUser
165 :type apiuser: AuthUser
166 :param group_name: Set the repository group name.
166 :param group_name: Set the repository group name.
167 :type group_name: str
167 :type group_name: str
168 :param description: Set the |repo| group description.
168 :param description: Set the |repo| group description.
169 :type description: str
169 :type description: str
170 :param owner: Set the |repo| group owner.
170 :param owner: Set the |repo| group owner.
171 :type owner: str
171 :type owner: str
172 :param copy_permissions:
172 :param copy_permissions:
173 :type copy_permissions:
173 :type copy_permissions:
174
174
175 Example output:
175 Example output:
176
176
177 .. code-block:: bash
177 .. code-block:: bash
178
178
179 id : <id_given_in_input>
179 id : <id_given_in_input>
180 result : {
180 result : {
181 "msg": "Created new repo group `<repo_group_name>`"
181 "msg": "Created new repo group `<repo_group_name>`"
182 "repo_group": <repogroup_object>
182 "repo_group": <repogroup_object>
183 }
183 }
184 error : null
184 error : null
185
185
186
186
187 Example error output:
187 Example error output:
188
188
189 .. code-block:: bash
189 .. code-block:: bash
190
190
191 id : <id_given_in_input>
191 id : <id_given_in_input>
192 result : null
192 result : null
193 error : {
193 error : {
194 failed to create repo group `<repogroupid>`
194 failed to create repo group `<repogroupid>`
195 }
195 }
196
196
197 """
197 """
198
198
199 owner = validate_set_owner_permissions(apiuser, owner)
199 owner = validate_set_owner_permissions(apiuser, owner)
200
200
201 description = Optional.extract(description)
201 description = Optional.extract(description)
202 copy_permissions = Optional.extract(copy_permissions)
202 copy_permissions = Optional.extract(copy_permissions)
203
203
204 schema = repo_group_schema.RepoGroupSchema().bind(
204 schema = repo_group_schema.RepoGroupSchema().bind(
205 # user caller
205 # user caller
206 user=apiuser)
206 user=apiuser)
207
207
208 try:
208 try:
209 schema_data = schema.deserialize(dict(
209 schema_data = schema.deserialize(dict(
210 repo_group_name=group_name,
210 repo_group_name=group_name,
211 repo_group_owner=owner.username,
211 repo_group_owner=owner.username,
212 repo_group_description=description,
212 repo_group_description=description,
213 repo_group_copy_permissions=copy_permissions,
213 repo_group_copy_permissions=copy_permissions,
214 ))
214 ))
215 except validation_schema.Invalid as err:
215 except validation_schema.Invalid as err:
216 raise JSONRPCValidationError(colander_exc=err)
216 raise JSONRPCValidationError(colander_exc=err)
217
217
218 validated_group_name = schema_data['repo_group_name']
218 validated_group_name = schema_data['repo_group_name']
219
219
220 try:
220 try:
221 repo_group = RepoGroupModel().create(
221 repo_group = RepoGroupModel().create(
222 owner=owner,
222 owner=owner,
223 group_name=validated_group_name,
223 group_name=validated_group_name,
224 group_description=schema_data['repo_group_name'],
224 group_description=schema_data['repo_group_name'],
225 copy_permissions=schema_data['repo_group_copy_permissions'])
225 copy_permissions=schema_data['repo_group_copy_permissions'])
226 Session().flush()
226 Session().flush()
227
227
228 repo_group_data = repo_group.get_api_data()
228 repo_group_data = repo_group.get_api_data()
229 audit_logger.store_api(
229 audit_logger.store_api(
230 'repo_group.create', action_data={'data': repo_group_data},
230 'repo_group.create', action_data={'data': repo_group_data},
231 user=apiuser)
231 user=apiuser)
232
232
233 Session().commit()
233 Session().commit()
234 return {
234 return {
235 'msg': 'Created new repo group `%s`' % validated_group_name,
235 'msg': 'Created new repo group `%s`' % validated_group_name,
236 'repo_group': repo_group.get_api_data()
236 'repo_group': repo_group.get_api_data()
237 }
237 }
238 except Exception:
238 except Exception:
239 log.exception("Exception occurred while trying create repo group")
239 log.exception("Exception occurred while trying create repo group")
240 raise JSONRPCError(
240 raise JSONRPCError(
241 'failed to create repo group `%s`' % (validated_group_name,))
241 'failed to create repo group `%s`' % (validated_group_name,))
242
242
243
243
244 @jsonrpc_method()
244 @jsonrpc_method()
245 def update_repo_group(
245 def update_repo_group(
246 request, apiuser, repogroupid, group_name=Optional(''),
246 request, apiuser, repogroupid, group_name=Optional(''),
247 description=Optional(''), owner=Optional(OAttr('apiuser')),
247 description=Optional(''), owner=Optional(OAttr('apiuser')),
248 enable_locking=Optional(False)):
248 enable_locking=Optional(False)):
249 """
249 """
250 Updates repository group with the details given.
250 Updates repository group with the details given.
251
251
252 This command can only be run using an |authtoken| with admin
252 This command can only be run using an |authtoken| with admin
253 permissions.
253 permissions.
254
254
255 * If the group_name name contains "/", repository group will be updated
255 * If the group_name name contains "/", repository group will be updated
256 accordingly with a repository group or nested repository groups
256 accordingly with a repository group or nested repository groups
257
257
258 For example repogroupid=group-test group_name="foo/bar/group-test"
258 For example repogroupid=group-test group_name="foo/bar/group-test"
259 will update repository group called "group-test" and place it
259 will update repository group called "group-test" and place it
260 inside group "foo/bar".
260 inside group "foo/bar".
261 You have to have permissions to access and write to the last repository
261 You have to have permissions to access and write to the last repository
262 group ("bar" in this example)
262 group ("bar" in this example)
263
263
264 :param apiuser: This is filled automatically from the |authtoken|.
264 :param apiuser: This is filled automatically from the |authtoken|.
265 :type apiuser: AuthUser
265 :type apiuser: AuthUser
266 :param repogroupid: Set the ID of repository group.
266 :param repogroupid: Set the ID of repository group.
267 :type repogroupid: str or int
267 :type repogroupid: str or int
268 :param group_name: Set the name of the |repo| group.
268 :param group_name: Set the name of the |repo| group.
269 :type group_name: str
269 :type group_name: str
270 :param description: Set a description for the group.
270 :param description: Set a description for the group.
271 :type description: str
271 :type description: str
272 :param owner: Set the |repo| group owner.
272 :param owner: Set the |repo| group owner.
273 :type owner: str
273 :type owner: str
274 :param enable_locking: Enable |repo| locking. The default is false.
274 :param enable_locking: Enable |repo| locking. The default is false.
275 :type enable_locking: bool
275 :type enable_locking: bool
276 """
276 """
277
277
278 repo_group = get_repo_group_or_error(repogroupid)
278 repo_group = get_repo_group_or_error(repogroupid)
279
279
280 if not has_superadmin_permission(apiuser):
280 if not has_superadmin_permission(apiuser):
281 validate_repo_group_permissions(
281 validate_repo_group_permissions(
282 apiuser, repogroupid, repo_group, ('group.admin',))
282 apiuser, repogroupid, repo_group, ('group.admin',))
283
283
284 updates = dict(
284 updates = dict(
285 group_name=group_name
285 group_name=group_name
286 if not isinstance(group_name, Optional) else repo_group.group_name,
286 if not isinstance(group_name, Optional) else repo_group.group_name,
287
287
288 group_description=description
288 group_description=description
289 if not isinstance(description, Optional) else repo_group.group_description,
289 if not isinstance(description, Optional) else repo_group.group_description,
290
290
291 user=owner
291 user=owner
292 if not isinstance(owner, Optional) else repo_group.user.username,
292 if not isinstance(owner, Optional) else repo_group.user.username,
293
293
294 enable_locking=enable_locking
294 enable_locking=enable_locking
295 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
295 if not isinstance(enable_locking, Optional) else repo_group.enable_locking
296 )
296 )
297
297
298 schema = repo_group_schema.RepoGroupSchema().bind(
298 schema = repo_group_schema.RepoGroupSchema().bind(
299 # user caller
299 # user caller
300 user=apiuser,
300 user=apiuser,
301 old_values=repo_group.get_api_data())
301 old_values=repo_group.get_api_data())
302
302
303 try:
303 try:
304 schema_data = schema.deserialize(dict(
304 schema_data = schema.deserialize(dict(
305 repo_group_name=updates['group_name'],
305 repo_group_name=updates['group_name'],
306 repo_group_owner=updates['user'],
306 repo_group_owner=updates['user'],
307 repo_group_description=updates['group_description'],
307 repo_group_description=updates['group_description'],
308 repo_group_enable_locking=updates['enable_locking'],
308 repo_group_enable_locking=updates['enable_locking'],
309 ))
309 ))
310 except validation_schema.Invalid as err:
310 except validation_schema.Invalid as err:
311 raise JSONRPCValidationError(colander_exc=err)
311 raise JSONRPCValidationError(colander_exc=err)
312
312
313 validated_updates = dict(
313 validated_updates = dict(
314 group_name=schema_data['repo_group']['repo_group_name_without_group'],
314 group_name=schema_data['repo_group']['repo_group_name_without_group'],
315 group_parent_id=schema_data['repo_group']['repo_group_id'],
315 group_parent_id=schema_data['repo_group']['repo_group_id'],
316 user=schema_data['repo_group_owner'],
316 user=schema_data['repo_group_owner'],
317 group_description=schema_data['repo_group_description'],
317 group_description=schema_data['repo_group_description'],
318 enable_locking=schema_data['repo_group_enable_locking'],
318 enable_locking=schema_data['repo_group_enable_locking'],
319 )
319 )
320
320
321 old_data = repo_group.get_api_data()
321 old_data = repo_group.get_api_data()
322 try:
322 try:
323 RepoGroupModel().update(repo_group, validated_updates)
323 RepoGroupModel().update(repo_group, validated_updates)
324 audit_logger.store_api(
324 audit_logger.store_api(
325 'repo_group.edit', action_data={'old_data': old_data},
325 'repo_group.edit', action_data={'old_data': old_data},
326 user=apiuser)
326 user=apiuser)
327
327
328 Session().commit()
328 Session().commit()
329 return {
329 return {
330 'msg': 'updated repository group ID:%s %s' % (
330 'msg': 'updated repository group ID:%s %s' % (
331 repo_group.group_id, repo_group.group_name),
331 repo_group.group_id, repo_group.group_name),
332 'repo_group': repo_group.get_api_data()
332 'repo_group': repo_group.get_api_data()
333 }
333 }
334 except Exception:
334 except Exception:
335 log.exception(
335 log.exception(
336 u"Exception occurred while trying update repo group %s",
336 u"Exception occurred while trying update repo group %s",
337 repogroupid)
337 repogroupid)
338 raise JSONRPCError('failed to update repository group `%s`'
338 raise JSONRPCError('failed to update repository group `%s`'
339 % (repogroupid,))
339 % (repogroupid,))
340
340
341
341
342 @jsonrpc_method()
342 @jsonrpc_method()
343 def delete_repo_group(request, apiuser, repogroupid):
343 def delete_repo_group(request, apiuser, repogroupid):
344 """
344 """
345 Deletes a |repo| group.
345 Deletes a |repo| group.
346
346
347 :param apiuser: This is filled automatically from the |authtoken|.
347 :param apiuser: This is filled automatically from the |authtoken|.
348 :type apiuser: AuthUser
348 :type apiuser: AuthUser
349 :param repogroupid: Set the name or ID of repository group to be
349 :param repogroupid: Set the name or ID of repository group to be
350 deleted.
350 deleted.
351 :type repogroupid: str or int
351 :type repogroupid: str or int
352
352
353 Example output:
353 Example output:
354
354
355 .. code-block:: bash
355 .. code-block:: bash
356
356
357 id : <id_given_in_input>
357 id : <id_given_in_input>
358 result : {
358 result : {
359 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
359 'msg': 'deleted repo group ID:<repogroupid> <repogroupname>'
360 'repo_group': null
360 'repo_group': null
361 }
361 }
362 error : null
362 error : null
363
363
364 Example error output:
364 Example error output:
365
365
366 .. code-block:: bash
366 .. code-block:: bash
367
367
368 id : <id_given_in_input>
368 id : <id_given_in_input>
369 result : null
369 result : null
370 error : {
370 error : {
371 "failed to delete repo group ID:<repogroupid> <repogroupname>"
371 "failed to delete repo group ID:<repogroupid> <repogroupname>"
372 }
372 }
373
373
374 """
374 """
375
375
376 repo_group = get_repo_group_or_error(repogroupid)
376 repo_group = get_repo_group_or_error(repogroupid)
377 if not has_superadmin_permission(apiuser):
377 if not has_superadmin_permission(apiuser):
378 validate_repo_group_permissions(
378 validate_repo_group_permissions(
379 apiuser, repogroupid, repo_group, ('group.admin',))
379 apiuser, repogroupid, repo_group, ('group.admin',))
380
380
381 old_data = repo_group.get_api_data()
381 old_data = repo_group.get_api_data()
382 try:
382 try:
383 RepoGroupModel().delete(repo_group)
383 RepoGroupModel().delete(repo_group)
384 audit_logger.store_api(
384 audit_logger.store_api(
385 'repo_group.delete', action_data={'old_data': old_data},
385 'repo_group.delete', action_data={'old_data': old_data},
386 user=apiuser)
386 user=apiuser)
387 Session().commit()
387 Session().commit()
388 return {
388 return {
389 'msg': 'deleted repo group ID:%s %s' %
389 'msg': 'deleted repo group ID:%s %s' %
390 (repo_group.group_id, repo_group.group_name),
390 (repo_group.group_id, repo_group.group_name),
391 'repo_group': None
391 'repo_group': None
392 }
392 }
393 except Exception:
393 except Exception:
394 log.exception("Exception occurred while trying to delete repo group")
394 log.exception("Exception occurred while trying to delete repo group")
395 raise JSONRPCError('failed to delete repo group ID:%s %s' %
395 raise JSONRPCError('failed to delete repo group ID:%s %s' %
396 (repo_group.group_id, repo_group.group_name))
396 (repo_group.group_id, repo_group.group_name))
397
397
398
398
399 @jsonrpc_method()
399 @jsonrpc_method()
400 def grant_user_permission_to_repo_group(
400 def grant_user_permission_to_repo_group(
401 request, apiuser, repogroupid, userid, perm,
401 request, apiuser, repogroupid, userid, perm,
402 apply_to_children=Optional('none')):
402 apply_to_children=Optional('none')):
403 """
403 """
404 Grant permission for a user on the given repository group, or update
404 Grant permission for a user on the given repository group, or update
405 existing permissions if found.
405 existing permissions if found.
406
406
407 This command can only be run using an |authtoken| with admin
407 This command can only be run using an |authtoken| with admin
408 permissions.
408 permissions.
409
409
410 :param apiuser: This is filled automatically from the |authtoken|.
410 :param apiuser: This is filled automatically from the |authtoken|.
411 :type apiuser: AuthUser
411 :type apiuser: AuthUser
412 :param repogroupid: Set the name or ID of repository group.
412 :param repogroupid: Set the name or ID of repository group.
413 :type repogroupid: str or int
413 :type repogroupid: str or int
414 :param userid: Set the user name.
414 :param userid: Set the user name.
415 :type userid: str
415 :type userid: str
416 :param perm: (group.(none|read|write|admin))
416 :param perm: (group.(none|read|write|admin))
417 :type perm: str
417 :type perm: str
418 :param apply_to_children: 'none', 'repos', 'groups', 'all'
418 :param apply_to_children: 'none', 'repos', 'groups', 'all'
419 :type apply_to_children: str
419 :type apply_to_children: str
420
420
421 Example output:
421 Example output:
422
422
423 .. code-block:: bash
423 .. code-block:: bash
424
424
425 id : <id_given_in_input>
425 id : <id_given_in_input>
426 result: {
426 result: {
427 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
427 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
428 "success": true
428 "success": true
429 }
429 }
430 error: null
430 error: null
431
431
432 Example error output:
432 Example error output:
433
433
434 .. code-block:: bash
434 .. code-block:: bash
435
435
436 id : <id_given_in_input>
436 id : <id_given_in_input>
437 result : null
437 result : null
438 error : {
438 error : {
439 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
439 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
440 }
440 }
441
441
442 """
442 """
443
443
444 repo_group = get_repo_group_or_error(repogroupid)
444 repo_group = get_repo_group_or_error(repogroupid)
445
445
446 if not has_superadmin_permission(apiuser):
446 if not has_superadmin_permission(apiuser):
447 validate_repo_group_permissions(
447 validate_repo_group_permissions(
448 apiuser, repogroupid, repo_group, ('group.admin',))
448 apiuser, repogroupid, repo_group, ('group.admin',))
449
449
450 user = get_user_or_error(userid)
450 user = get_user_or_error(userid)
451 perm = get_perm_or_error(perm, prefix='group.')
451 perm = get_perm_or_error(perm, prefix='group.')
452 apply_to_children = Optional.extract(apply_to_children)
452 apply_to_children = Optional.extract(apply_to_children)
453
453
454 perm_additions = [[user.user_id, perm, "user"]]
454 perm_additions = [[user.user_id, perm, "user"]]
455 try:
455 try:
456 RepoGroupModel().update_permissions(repo_group=repo_group,
456 RepoGroupModel().update_permissions(repo_group=repo_group,
457 perm_additions=perm_additions,
457 perm_additions=perm_additions,
458 recursive=apply_to_children,
458 recursive=apply_to_children,
459 cur_user=apiuser)
459 cur_user=apiuser)
460 Session().commit()
460 Session().commit()
461 return {
461 return {
462 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
462 'msg': 'Granted perm: `%s` (recursive:%s) for user: '
463 '`%s` in repo group: `%s`' % (
463 '`%s` in repo group: `%s`' % (
464 perm.permission_name, apply_to_children, user.username,
464 perm.permission_name, apply_to_children, user.username,
465 repo_group.name
465 repo_group.name
466 ),
466 ),
467 'success': True
467 'success': True
468 }
468 }
469 except Exception:
469 except Exception:
470 log.exception("Exception occurred while trying to grant "
470 log.exception("Exception occurred while trying to grant "
471 "user permissions to repo group")
471 "user permissions to repo group")
472 raise JSONRPCError(
472 raise JSONRPCError(
473 'failed to edit permission for user: '
473 'failed to edit permission for user: '
474 '`%s` in repo group: `%s`' % (userid, repo_group.name))
474 '`%s` in repo group: `%s`' % (userid, repo_group.name))
475
475
476
476
477 @jsonrpc_method()
477 @jsonrpc_method()
478 def revoke_user_permission_from_repo_group(
478 def revoke_user_permission_from_repo_group(
479 request, apiuser, repogroupid, userid,
479 request, apiuser, repogroupid, userid,
480 apply_to_children=Optional('none')):
480 apply_to_children=Optional('none')):
481 """
481 """
482 Revoke permission for a user in a given repository group.
482 Revoke permission for a user in a given repository group.
483
483
484 This command can only be run using an |authtoken| with admin
484 This command can only be run using an |authtoken| with admin
485 permissions on the |repo| group.
485 permissions on the |repo| group.
486
486
487 :param apiuser: This is filled automatically from the |authtoken|.
487 :param apiuser: This is filled automatically from the |authtoken|.
488 :type apiuser: AuthUser
488 :type apiuser: AuthUser
489 :param repogroupid: Set the name or ID of the repository group.
489 :param repogroupid: Set the name or ID of the repository group.
490 :type repogroupid: str or int
490 :type repogroupid: str or int
491 :param userid: Set the user name to revoke.
491 :param userid: Set the user name to revoke.
492 :type userid: str
492 :type userid: str
493 :param apply_to_children: 'none', 'repos', 'groups', 'all'
493 :param apply_to_children: 'none', 'repos', 'groups', 'all'
494 :type apply_to_children: str
494 :type apply_to_children: str
495
495
496 Example output:
496 Example output:
497
497
498 .. code-block:: bash
498 .. code-block:: bash
499
499
500 id : <id_given_in_input>
500 id : <id_given_in_input>
501 result: {
501 result: {
502 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
502 "msg" : "Revoked perm (recursive:<apply_to_children>) for user: `<username>` in repo group: `<repo_group_name>`",
503 "success": true
503 "success": true
504 }
504 }
505 error: null
505 error: null
506
506
507 Example error output:
507 Example error output:
508
508
509 .. code-block:: bash
509 .. code-block:: bash
510
510
511 id : <id_given_in_input>
511 id : <id_given_in_input>
512 result : null
512 result : null
513 error : {
513 error : {
514 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
514 "failed to edit permission for user: `<userid>` in repo group: `<repo_group_name>`"
515 }
515 }
516
516
517 """
517 """
518
518
519 repo_group = get_repo_group_or_error(repogroupid)
519 repo_group = get_repo_group_or_error(repogroupid)
520
520
521 if not has_superadmin_permission(apiuser):
521 if not has_superadmin_permission(apiuser):
522 validate_repo_group_permissions(
522 validate_repo_group_permissions(
523 apiuser, repogroupid, repo_group, ('group.admin',))
523 apiuser, repogroupid, repo_group, ('group.admin',))
524
524
525 user = get_user_or_error(userid)
525 user = get_user_or_error(userid)
526 apply_to_children = Optional.extract(apply_to_children)
526 apply_to_children = Optional.extract(apply_to_children)
527
527
528 perm_deletions = [[user.user_id, None, "user"]]
528 perm_deletions = [[user.user_id, None, "user"]]
529 try:
529 try:
530 RepoGroupModel().update_permissions(repo_group=repo_group,
530 RepoGroupModel().update_permissions(repo_group=repo_group,
531 perm_deletions=perm_deletions,
531 perm_deletions=perm_deletions,
532 recursive=apply_to_children,
532 recursive=apply_to_children,
533 cur_user=apiuser)
533 cur_user=apiuser)
534 Session().commit()
534 Session().commit()
535 return {
535 return {
536 'msg': 'Revoked perm (recursive:%s) for user: '
536 'msg': 'Revoked perm (recursive:%s) for user: '
537 '`%s` in repo group: `%s`' % (
537 '`%s` in repo group: `%s`' % (
538 apply_to_children, user.username, repo_group.name
538 apply_to_children, user.username, repo_group.name
539 ),
539 ),
540 'success': True
540 'success': True
541 }
541 }
542 except Exception:
542 except Exception:
543 log.exception("Exception occurred while trying revoke user "
543 log.exception("Exception occurred while trying revoke user "
544 "permission from repo group")
544 "permission from repo group")
545 raise JSONRPCError(
545 raise JSONRPCError(
546 'failed to edit permission for user: '
546 'failed to edit permission for user: '
547 '`%s` in repo group: `%s`' % (userid, repo_group.name))
547 '`%s` in repo group: `%s`' % (userid, repo_group.name))
548
548
549
549
550 @jsonrpc_method()
550 @jsonrpc_method()
551 def grant_user_group_permission_to_repo_group(
551 def grant_user_group_permission_to_repo_group(
552 request, apiuser, repogroupid, usergroupid, perm,
552 request, apiuser, repogroupid, usergroupid, perm,
553 apply_to_children=Optional('none'), ):
553 apply_to_children=Optional('none'), ):
554 """
554 """
555 Grant permission for a user group on given repository group, or update
555 Grant permission for a user group on given repository group, or update
556 existing permissions if found.
556 existing permissions if found.
557
557
558 This command can only be run using an |authtoken| with admin
558 This command can only be run using an |authtoken| with admin
559 permissions on the |repo| group.
559 permissions on the |repo| group.
560
560
561 :param apiuser: This is filled automatically from the |authtoken|.
561 :param apiuser: This is filled automatically from the |authtoken|.
562 :type apiuser: AuthUser
562 :type apiuser: AuthUser
563 :param repogroupid: Set the name or id of repository group
563 :param repogroupid: Set the name or id of repository group
564 :type repogroupid: str or int
564 :type repogroupid: str or int
565 :param usergroupid: id of usergroup
565 :param usergroupid: id of usergroup
566 :type usergroupid: str or int
566 :type usergroupid: str or int
567 :param perm: (group.(none|read|write|admin))
567 :param perm: (group.(none|read|write|admin))
568 :type perm: str
568 :type perm: str
569 :param apply_to_children: 'none', 'repos', 'groups', 'all'
569 :param apply_to_children: 'none', 'repos', 'groups', 'all'
570 :type apply_to_children: str
570 :type apply_to_children: str
571
571
572 Example output:
572 Example output:
573
573
574 .. code-block:: bash
574 .. code-block:: bash
575
575
576 id : <id_given_in_input>
576 id : <id_given_in_input>
577 result : {
577 result : {
578 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
578 "msg" : "Granted perm: `<perm>` (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
579 "success": true
579 "success": true
580
580
581 }
581 }
582 error : null
582 error : null
583
583
584 Example error output:
584 Example error output:
585
585
586 .. code-block:: bash
586 .. code-block:: bash
587
587
588 id : <id_given_in_input>
588 id : <id_given_in_input>
589 result : null
589 result : null
590 error : {
590 error : {
591 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
591 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
592 }
592 }
593
593
594 """
594 """
595
595
596 repo_group = get_repo_group_or_error(repogroupid)
596 repo_group = get_repo_group_or_error(repogroupid)
597 perm = get_perm_or_error(perm, prefix='group.')
597 perm = get_perm_or_error(perm, prefix='group.')
598 user_group = get_user_group_or_error(usergroupid)
598 user_group = get_user_group_or_error(usergroupid)
599 if not has_superadmin_permission(apiuser):
599 if not has_superadmin_permission(apiuser):
600 validate_repo_group_permissions(
600 validate_repo_group_permissions(
601 apiuser, repogroupid, repo_group, ('group.admin',))
601 apiuser, repogroupid, repo_group, ('group.admin',))
602
602
603 # check if we have at least read permission for this user group !
603 # check if we have at least read permission for this user group !
604 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
604 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
605 if not HasUserGroupPermissionAnyApi(*_perms)(
605 if not HasUserGroupPermissionAnyApi(*_perms)(
606 user=apiuser, user_group_name=user_group.users_group_name):
606 user=apiuser, user_group_name=user_group.users_group_name):
607 raise JSONRPCError(
607 raise JSONRPCError(
608 'user group `%s` does not exist' % (usergroupid,))
608 'user group `%s` does not exist' % (usergroupid,))
609
609
610 apply_to_children = Optional.extract(apply_to_children)
610 apply_to_children = Optional.extract(apply_to_children)
611
611
612 perm_additions = [[user_group.users_group_id, perm, "user_group"]]
612 perm_additions = [[user_group.users_group_id, perm, "user_group"]]
613 try:
613 try:
614 RepoGroupModel().update_permissions(repo_group=repo_group,
614 RepoGroupModel().update_permissions(repo_group=repo_group,
615 perm_additions=perm_additions,
615 perm_additions=perm_additions,
616 recursive=apply_to_children,
616 recursive=apply_to_children,
617 cur_user=apiuser)
617 cur_user=apiuser)
618 Session().commit()
618 Session().commit()
619 return {
619 return {
620 'msg': 'Granted perm: `%s` (recursive:%s) '
620 'msg': 'Granted perm: `%s` (recursive:%s) '
621 'for user group: `%s` in repo group: `%s`' % (
621 'for user group: `%s` in repo group: `%s`' % (
622 perm.permission_name, apply_to_children,
622 perm.permission_name, apply_to_children,
623 user_group.users_group_name, repo_group.name
623 user_group.users_group_name, repo_group.name
624 ),
624 ),
625 'success': True
625 'success': True
626 }
626 }
627 except Exception:
627 except Exception:
628 log.exception("Exception occurred while trying to grant user "
628 log.exception("Exception occurred while trying to grant user "
629 "group permissions to repo group")
629 "group permissions to repo group")
630 raise JSONRPCError(
630 raise JSONRPCError(
631 'failed to edit permission for user group: `%s` in '
631 'failed to edit permission for user group: `%s` in '
632 'repo group: `%s`' % (
632 'repo group: `%s`' % (
633 usergroupid, repo_group.name
633 usergroupid, repo_group.name
634 )
634 )
635 )
635 )
636
636
637
637
638 @jsonrpc_method()
638 @jsonrpc_method()
639 def revoke_user_group_permission_from_repo_group(
639 def revoke_user_group_permission_from_repo_group(
640 request, apiuser, repogroupid, usergroupid,
640 request, apiuser, repogroupid, usergroupid,
641 apply_to_children=Optional('none')):
641 apply_to_children=Optional('none')):
642 """
642 """
643 Revoke permission for user group on given repository.
643 Revoke permission for user group on given repository.
644
644
645 This command can only be run using an |authtoken| with admin
645 This command can only be run using an |authtoken| with admin
646 permissions on the |repo| group.
646 permissions on the |repo| group.
647
647
648 :param apiuser: This is filled automatically from the |authtoken|.
648 :param apiuser: This is filled automatically from the |authtoken|.
649 :type apiuser: AuthUser
649 :type apiuser: AuthUser
650 :param repogroupid: name or id of repository group
650 :param repogroupid: name or id of repository group
651 :type repogroupid: str or int
651 :type repogroupid: str or int
652 :param usergroupid:
652 :param usergroupid:
653 :param apply_to_children: 'none', 'repos', 'groups', 'all'
653 :param apply_to_children: 'none', 'repos', 'groups', 'all'
654 :type apply_to_children: str
654 :type apply_to_children: str
655
655
656 Example output:
656 Example output:
657
657
658 .. code-block:: bash
658 .. code-block:: bash
659
659
660 id : <id_given_in_input>
660 id : <id_given_in_input>
661 result: {
661 result: {
662 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
662 "msg" : "Revoked perm (recursive:<apply_to_children>) for user group: `<usersgroupname>` in repo group: `<repo_group_name>`",
663 "success": true
663 "success": true
664 }
664 }
665 error: null
665 error: null
666
666
667 Example error output:
667 Example error output:
668
668
669 .. code-block:: bash
669 .. code-block:: bash
670
670
671 id : <id_given_in_input>
671 id : <id_given_in_input>
672 result : null
672 result : null
673 error : {
673 error : {
674 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
674 "failed to edit permission for user group: `<usergroup>` in repo group: `<repo_group_name>`"
675 }
675 }
676
676
677
677
678 """
678 """
679
679
680 repo_group = get_repo_group_or_error(repogroupid)
680 repo_group = get_repo_group_or_error(repogroupid)
681 user_group = get_user_group_or_error(usergroupid)
681 user_group = get_user_group_or_error(usergroupid)
682 if not has_superadmin_permission(apiuser):
682 if not has_superadmin_permission(apiuser):
683 validate_repo_group_permissions(
683 validate_repo_group_permissions(
684 apiuser, repogroupid, repo_group, ('group.admin',))
684 apiuser, repogroupid, repo_group, ('group.admin',))
685
685
686 # check if we have at least read permission for this user group !
686 # check if we have at least read permission for this user group !
687 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
687 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
688 if not HasUserGroupPermissionAnyApi(*_perms)(
688 if not HasUserGroupPermissionAnyApi(*_perms)(
689 user=apiuser, user_group_name=user_group.users_group_name):
689 user=apiuser, user_group_name=user_group.users_group_name):
690 raise JSONRPCError(
690 raise JSONRPCError(
691 'user group `%s` does not exist' % (usergroupid,))
691 'user group `%s` does not exist' % (usergroupid,))
692
692
693 apply_to_children = Optional.extract(apply_to_children)
693 apply_to_children = Optional.extract(apply_to_children)
694
694
695 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
695 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
696 try:
696 try:
697 RepoGroupModel().update_permissions(repo_group=repo_group,
697 RepoGroupModel().update_permissions(repo_group=repo_group,
698 perm_deletions=perm_deletions,
698 perm_deletions=perm_deletions,
699 recursive=apply_to_children,
699 recursive=apply_to_children,
700 cur_user=apiuser)
700 cur_user=apiuser)
701 Session().commit()
701 Session().commit()
702 return {
702 return {
703 'msg': 'Revoked perm (recursive:%s) for user group: '
703 'msg': 'Revoked perm (recursive:%s) for user group: '
704 '`%s` in repo group: `%s`' % (
704 '`%s` in repo group: `%s`' % (
705 apply_to_children, user_group.users_group_name,
705 apply_to_children, user_group.users_group_name,
706 repo_group.name
706 repo_group.name
707 ),
707 ),
708 'success': True
708 'success': True
709 }
709 }
710 except Exception:
710 except Exception:
711 log.exception("Exception occurred while trying revoke user group "
711 log.exception("Exception occurred while trying revoke user group "
712 "permissions from repo group")
712 "permissions from repo group")
713 raise JSONRPCError(
713 raise JSONRPCError(
714 'failed to edit permission for user group: '
714 'failed to edit permission for user group: '
715 '`%s` in repo group: `%s`' % (
715 '`%s` in repo group: `%s`' % (
716 user_group.users_group_name, repo_group.name
716 user_group.users_group_name, repo_group.name
717 )
717 )
718 )
718 )
719
719
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now