##// END OF EJS Templates
code: update copyrights to 2020
marcink -
r4306:09801de9 default
parent child Browse files
Show More

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

@@ -1,57 +1,57 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import sys
22 import sys
23 import platform
23 import platform
24
24
25 VERSION = tuple(open(os.path.join(
25 VERSION = tuple(open(os.path.join(
26 os.path.dirname(__file__), 'VERSION')).read().split('.'))
26 os.path.dirname(__file__), 'VERSION')).read().split('.'))
27
27
28 BACKENDS = {
28 BACKENDS = {
29 'hg': 'Mercurial repository',
29 'hg': 'Mercurial repository',
30 'git': 'Git repository',
30 'git': 'Git repository',
31 'svn': 'Subversion repository',
31 'svn': 'Subversion repository',
32 }
32 }
33
33
34 CELERY_ENABLED = False
34 CELERY_ENABLED = False
35 CELERY_EAGER = False
35 CELERY_EAGER = False
36
36
37 # link to config for pyramid
37 # link to config for pyramid
38 CONFIG = {}
38 CONFIG = {}
39
39
40 # Populated with the settings dictionary from application init in
40 # Populated with the settings dictionary from application init in
41 # rhodecode.conf.environment.load_pyramid_environment
41 # rhodecode.conf.environment.load_pyramid_environment
42 PYRAMID_SETTINGS = {}
42 PYRAMID_SETTINGS = {}
43
43
44 # Linked module for extensions
44 # Linked module for extensions
45 EXTENSIONS = {}
45 EXTENSIONS = {}
46
46
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
48 __dbversion__ = 105 # defines current db version for migrations
48 __dbversion__ = 105 # defines current db version for migrations
49 __platform__ = platform.system()
49 __platform__ = platform.system()
50 __license__ = 'AGPLv3, and Commercial License'
50 __license__ = 'AGPLv3, and Commercial License'
51 __author__ = 'RhodeCode GmbH'
51 __author__ = 'RhodeCode GmbH'
52 __url__ = 'https://code.rhodecode.com'
52 __url__ = 'https://code.rhodecode.com'
53
53
54 is_windows = __platform__ in ['Windows']
54 is_windows = __platform__ in ['Windows']
55 is_unix = not is_windows
55 is_unix = not is_windows
56 is_test = False
56 is_test = False
57 disable_error_handler = False
57 disable_error_handler = False
@@ -1,555 +1,555 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import itertools
21 import itertools
22 import logging
22 import logging
23 import sys
23 import sys
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.exc_tracking import store_exception
41 from rhodecode.lib.exc_tracking import store_exception
42 from rhodecode.lib.ext_json import json
42 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.lib.utils2 import safe_str
44 from rhodecode.lib.plugins.utils import get_plugin_settings
44 from rhodecode.lib.plugins.utils import get_plugin_settings
45 from rhodecode.model.db import User, UserApiKeys
45 from rhodecode.model.db import User, UserApiKeys
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49 DEFAULT_RENDERER = 'jsonrpc_renderer'
49 DEFAULT_RENDERER = 'jsonrpc_renderer'
50 DEFAULT_URL = '/_admin/apiv2'
50 DEFAULT_URL = '/_admin/apiv2'
51
51
52
52
53 def find_methods(jsonrpc_methods, pattern):
53 def find_methods(jsonrpc_methods, pattern):
54 matches = OrderedDict()
54 matches = OrderedDict()
55 if not isinstance(pattern, (list, tuple)):
55 if not isinstance(pattern, (list, tuple)):
56 pattern = [pattern]
56 pattern = [pattern]
57
57
58 for single_pattern in pattern:
58 for single_pattern in pattern:
59 for method_name, method in jsonrpc_methods.items():
59 for method_name, method in jsonrpc_methods.items():
60 if fnmatch.fnmatch(method_name, single_pattern):
60 if fnmatch.fnmatch(method_name, single_pattern):
61 matches[method_name] = method
61 matches[method_name] = method
62 return matches
62 return matches
63
63
64
64
65 class ExtJsonRenderer(object):
65 class ExtJsonRenderer(object):
66 """
66 """
67 Custom renderer that mkaes use of our ext_json lib
67 Custom renderer that mkaes use of our ext_json lib
68
68
69 """
69 """
70
70
71 def __init__(self, serializer=json.dumps, **kw):
71 def __init__(self, serializer=json.dumps, **kw):
72 """ Any keyword arguments will be passed to the ``serializer``
72 """ Any keyword arguments will be passed to the ``serializer``
73 function."""
73 function."""
74 self.serializer = serializer
74 self.serializer = serializer
75 self.kw = kw
75 self.kw = kw
76
76
77 def __call__(self, info):
77 def __call__(self, info):
78 """ Returns a plain JSON-encoded string with content-type
78 """ Returns a plain JSON-encoded string with content-type
79 ``application/json``. The content-type may be overridden by
79 ``application/json``. The content-type may be overridden by
80 setting ``request.response.content_type``."""
80 setting ``request.response.content_type``."""
81
81
82 def _render(value, system):
82 def _render(value, system):
83 request = system.get('request')
83 request = system.get('request')
84 if request is not None:
84 if request is not None:
85 response = request.response
85 response = request.response
86 ct = response.content_type
86 ct = response.content_type
87 if ct == response.default_content_type:
87 if ct == response.default_content_type:
88 response.content_type = 'application/json'
88 response.content_type = 'application/json'
89
89
90 return self.serializer(value, **self.kw)
90 return self.serializer(value, **self.kw)
91
91
92 return _render
92 return _render
93
93
94
94
95 def jsonrpc_response(request, result):
95 def jsonrpc_response(request, result):
96 rpc_id = getattr(request, 'rpc_id', None)
96 rpc_id = getattr(request, 'rpc_id', None)
97 response = request.response
97 response = request.response
98
98
99 # store content_type before render is called
99 # store content_type before render is called
100 ct = response.content_type
100 ct = response.content_type
101
101
102 ret_value = ''
102 ret_value = ''
103 if rpc_id:
103 if rpc_id:
104 ret_value = {
104 ret_value = {
105 'id': rpc_id,
105 'id': rpc_id,
106 'result': result,
106 'result': result,
107 'error': None,
107 'error': None,
108 }
108 }
109
109
110 # fetch deprecation warnings, and store it inside results
110 # fetch deprecation warnings, and store it inside results
111 deprecation = getattr(request, 'rpc_deprecation', None)
111 deprecation = getattr(request, 'rpc_deprecation', None)
112 if deprecation:
112 if deprecation:
113 ret_value['DEPRECATION_WARNING'] = deprecation
113 ret_value['DEPRECATION_WARNING'] = deprecation
114
114
115 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
115 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
116 response.body = safe_str(raw_body, response.charset)
116 response.body = safe_str(raw_body, response.charset)
117
117
118 if ct == response.default_content_type:
118 if ct == response.default_content_type:
119 response.content_type = 'application/json'
119 response.content_type = 'application/json'
120
120
121 return response
121 return response
122
122
123
123
124 def jsonrpc_error(request, message, retid=None, code=None, headers=None):
124 def jsonrpc_error(request, message, retid=None, code=None, headers=None):
125 """
125 """
126 Generate a Response object with a JSON-RPC error body
126 Generate a Response object with a JSON-RPC error body
127
127
128 :param code:
128 :param code:
129 :param retid:
129 :param retid:
130 :param message:
130 :param message:
131 """
131 """
132 err_dict = {'id': retid, 'result': None, 'error': message}
132 err_dict = {'id': retid, 'result': None, 'error': message}
133 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
133 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
134
134
135 return Response(
135 return Response(
136 body=body,
136 body=body,
137 status=code,
137 status=code,
138 content_type='application/json',
138 content_type='application/json',
139 headerlist=headers
139 headerlist=headers
140 )
140 )
141
141
142
142
143 def exception_view(exc, request):
143 def exception_view(exc, request):
144 rpc_id = getattr(request, 'rpc_id', None)
144 rpc_id = getattr(request, 'rpc_id', None)
145
145
146 if isinstance(exc, JSONRPCError):
146 if isinstance(exc, JSONRPCError):
147 fault_message = safe_str(exc.message)
147 fault_message = safe_str(exc.message)
148 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
148 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
149 elif isinstance(exc, JSONRPCValidationError):
149 elif isinstance(exc, JSONRPCValidationError):
150 colander_exc = exc.colander_exception
150 colander_exc = exc.colander_exception
151 # TODO(marcink): think maybe of nicer way to serialize errors ?
151 # TODO(marcink): think maybe of nicer way to serialize errors ?
152 fault_message = colander_exc.asdict()
152 fault_message = colander_exc.asdict()
153 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
153 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
154 elif isinstance(exc, JSONRPCForbidden):
154 elif isinstance(exc, JSONRPCForbidden):
155 fault_message = 'Access was denied to this resource.'
155 fault_message = 'Access was denied to this resource.'
156 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
156 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
157 elif isinstance(exc, HTTPNotFound):
157 elif isinstance(exc, HTTPNotFound):
158 method = request.rpc_method
158 method = request.rpc_method
159 log.debug('json-rpc method `%s` not found in list of '
159 log.debug('json-rpc method `%s` not found in list of '
160 'api calls: %s, rpc_id:%s',
160 'api calls: %s, rpc_id:%s',
161 method, request.registry.jsonrpc_methods.keys(), rpc_id)
161 method, request.registry.jsonrpc_methods.keys(), rpc_id)
162
162
163 similar = 'none'
163 similar = 'none'
164 try:
164 try:
165 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
165 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
166 similar_found = find_methods(
166 similar_found = find_methods(
167 request.registry.jsonrpc_methods, similar_paterns)
167 request.registry.jsonrpc_methods, similar_paterns)
168 similar = ', '.join(similar_found.keys()) or similar
168 similar = ', '.join(similar_found.keys()) or similar
169 except Exception:
169 except Exception:
170 # make the whole above block safe
170 # make the whole above block safe
171 pass
171 pass
172
172
173 fault_message = "No such method: {}. Similar methods: {}".format(
173 fault_message = "No such method: {}. Similar methods: {}".format(
174 method, similar)
174 method, similar)
175 else:
175 else:
176 fault_message = 'undefined error'
176 fault_message = 'undefined error'
177 exc_info = exc.exc_info()
177 exc_info = exc.exc_info()
178 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
178 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
179
179
180 return jsonrpc_error(request, fault_message, rpc_id)
180 return jsonrpc_error(request, fault_message, rpc_id)
181
181
182
182
183 def request_view(request):
183 def request_view(request):
184 """
184 """
185 Main request handling method. It handles all logic to call a specific
185 Main request handling method. It handles all logic to call a specific
186 exposed method
186 exposed method
187 """
187 """
188 # cython compatible inspect
188 # cython compatible inspect
189 from rhodecode.config.patches import inspect_getargspec
189 from rhodecode.config.patches import inspect_getargspec
190 inspect = inspect_getargspec()
190 inspect = inspect_getargspec()
191
191
192 # check if we can find this session using api_key, get_by_auth_token
192 # check if we can find this session using api_key, get_by_auth_token
193 # search not expired tokens only
193 # search not expired tokens only
194 try:
194 try:
195 api_user = User.get_by_auth_token(request.rpc_api_key)
195 api_user = User.get_by_auth_token(request.rpc_api_key)
196
196
197 if api_user is None:
197 if api_user is None:
198 return jsonrpc_error(
198 return jsonrpc_error(
199 request, retid=request.rpc_id, message='Invalid API KEY')
199 request, retid=request.rpc_id, message='Invalid API KEY')
200
200
201 if not api_user.active:
201 if not api_user.active:
202 return jsonrpc_error(
202 return jsonrpc_error(
203 request, retid=request.rpc_id,
203 request, retid=request.rpc_id,
204 message='Request from this user not allowed')
204 message='Request from this user not allowed')
205
205
206 # check if we are allowed to use this IP
206 # check if we are allowed to use this IP
207 auth_u = AuthUser(
207 auth_u = AuthUser(
208 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
208 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
209 if not auth_u.ip_allowed:
209 if not auth_u.ip_allowed:
210 return jsonrpc_error(
210 return jsonrpc_error(
211 request, retid=request.rpc_id,
211 request, retid=request.rpc_id,
212 message='Request from IP:%s not allowed' % (
212 message='Request from IP:%s not allowed' % (
213 request.rpc_ip_addr,))
213 request.rpc_ip_addr,))
214 else:
214 else:
215 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
215 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
216
216
217 # register our auth-user
217 # register our auth-user
218 request.rpc_user = auth_u
218 request.rpc_user = auth_u
219 request.environ['rc_auth_user_id'] = auth_u.user_id
219 request.environ['rc_auth_user_id'] = auth_u.user_id
220
220
221 # now check if token is valid for API
221 # now check if token is valid for API
222 auth_token = request.rpc_api_key
222 auth_token = request.rpc_api_key
223 token_match = api_user.authenticate_by_token(
223 token_match = api_user.authenticate_by_token(
224 auth_token, roles=[UserApiKeys.ROLE_API])
224 auth_token, roles=[UserApiKeys.ROLE_API])
225 invalid_token = not token_match
225 invalid_token = not token_match
226
226
227 log.debug('Checking if API KEY is valid with proper role')
227 log.debug('Checking if API KEY is valid with proper role')
228 if invalid_token:
228 if invalid_token:
229 return jsonrpc_error(
229 return jsonrpc_error(
230 request, retid=request.rpc_id,
230 request, retid=request.rpc_id,
231 message='API KEY invalid or, has bad role for an API call')
231 message='API KEY invalid or, has bad role for an API call')
232
232
233 except Exception:
233 except Exception:
234 log.exception('Error on API AUTH')
234 log.exception('Error on API AUTH')
235 return jsonrpc_error(
235 return jsonrpc_error(
236 request, retid=request.rpc_id, message='Invalid API KEY')
236 request, retid=request.rpc_id, message='Invalid API KEY')
237
237
238 method = request.rpc_method
238 method = request.rpc_method
239 func = request.registry.jsonrpc_methods[method]
239 func = request.registry.jsonrpc_methods[method]
240
240
241 # now that we have a method, add request._req_params to
241 # now that we have a method, add request._req_params to
242 # self.kargs and dispatch control to WGIController
242 # self.kargs and dispatch control to WGIController
243 argspec = inspect.getargspec(func)
243 argspec = inspect.getargspec(func)
244 arglist = argspec[0]
244 arglist = argspec[0]
245 defaults = map(type, argspec[3] or [])
245 defaults = map(type, argspec[3] or [])
246 default_empty = types.NotImplementedType
246 default_empty = types.NotImplementedType
247
247
248 # kw arguments required by this method
248 # kw arguments required by this method
249 func_kwargs = dict(itertools.izip_longest(
249 func_kwargs = dict(itertools.izip_longest(
250 reversed(arglist), reversed(defaults), fillvalue=default_empty))
250 reversed(arglist), reversed(defaults), fillvalue=default_empty))
251
251
252 # This attribute will need to be first param of a method that uses
252 # This attribute will need to be first param of a method that uses
253 # api_key, which is translated to instance of user at that name
253 # api_key, which is translated to instance of user at that name
254 user_var = 'apiuser'
254 user_var = 'apiuser'
255 request_var = 'request'
255 request_var = 'request'
256
256
257 for arg in [user_var, request_var]:
257 for arg in [user_var, request_var]:
258 if arg not in arglist:
258 if arg not in arglist:
259 return jsonrpc_error(
259 return jsonrpc_error(
260 request,
260 request,
261 retid=request.rpc_id,
261 retid=request.rpc_id,
262 message='This method [%s] does not support '
262 message='This method [%s] does not support '
263 'required parameter `%s`' % (func.__name__, arg))
263 'required parameter `%s`' % (func.__name__, arg))
264
264
265 # get our arglist and check if we provided them as args
265 # get our arglist and check if we provided them as args
266 for arg, default in func_kwargs.items():
266 for arg, default in func_kwargs.items():
267 if arg in [user_var, request_var]:
267 if arg in [user_var, request_var]:
268 # user_var and request_var are pre-hardcoded parameters and we
268 # user_var and request_var are pre-hardcoded parameters and we
269 # don't need to do any translation
269 # don't need to do any translation
270 continue
270 continue
271
271
272 # skip the required param check if it's default value is
272 # skip the required param check if it's default value is
273 # NotImplementedType (default_empty)
273 # NotImplementedType (default_empty)
274 if default == default_empty and arg not in request.rpc_params:
274 if default == default_empty and arg not in request.rpc_params:
275 return jsonrpc_error(
275 return jsonrpc_error(
276 request,
276 request,
277 retid=request.rpc_id,
277 retid=request.rpc_id,
278 message=('Missing non optional `%s` arg in JSON DATA' % arg)
278 message=('Missing non optional `%s` arg in JSON DATA' % arg)
279 )
279 )
280
280
281 # sanitize extra passed arguments
281 # sanitize extra passed arguments
282 for k in request.rpc_params.keys()[:]:
282 for k in request.rpc_params.keys()[:]:
283 if k not in func_kwargs:
283 if k not in func_kwargs:
284 del request.rpc_params[k]
284 del request.rpc_params[k]
285
285
286 call_params = request.rpc_params
286 call_params = request.rpc_params
287 call_params.update({
287 call_params.update({
288 'request': request,
288 'request': request,
289 'apiuser': auth_u
289 'apiuser': auth_u
290 })
290 })
291
291
292 # register some common functions for usage
292 # register some common functions for usage
293 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
293 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
294
294
295 try:
295 try:
296 ret_value = func(**call_params)
296 ret_value = func(**call_params)
297 return jsonrpc_response(request, ret_value)
297 return jsonrpc_response(request, ret_value)
298 except JSONRPCBaseError:
298 except JSONRPCBaseError:
299 raise
299 raise
300 except Exception:
300 except Exception:
301 log.exception('Unhandled exception occurred on api call: %s', func)
301 log.exception('Unhandled exception occurred on api call: %s', func)
302 exc_info = sys.exc_info()
302 exc_info = sys.exc_info()
303 exc_id, exc_type_name = store_exception(
303 exc_id, exc_type_name = store_exception(
304 id(exc_info), exc_info, prefix='rhodecode-api')
304 id(exc_info), exc_info, prefix='rhodecode-api')
305 error_headers = [('RhodeCode-Exception-Id', str(exc_id)),
305 error_headers = [('RhodeCode-Exception-Id', str(exc_id)),
306 ('RhodeCode-Exception-Type', str(exc_type_name))]
306 ('RhodeCode-Exception-Type', str(exc_type_name))]
307 return jsonrpc_error(
307 return jsonrpc_error(
308 request, retid=request.rpc_id, message='Internal server error',
308 request, retid=request.rpc_id, message='Internal server error',
309 headers=error_headers)
309 headers=error_headers)
310
310
311
311
312 def setup_request(request):
312 def setup_request(request):
313 """
313 """
314 Parse a JSON-RPC request body. It's used inside the predicates method
314 Parse a JSON-RPC request body. It's used inside the predicates method
315 to validate and bootstrap requests for usage in rpc calls.
315 to validate and bootstrap requests for usage in rpc calls.
316
316
317 We need to raise JSONRPCError here if we want to return some errors back to
317 We need to raise JSONRPCError here if we want to return some errors back to
318 user.
318 user.
319 """
319 """
320
320
321 log.debug('Executing setup request: %r', request)
321 log.debug('Executing setup request: %r', request)
322 request.rpc_ip_addr = get_ip_addr(request.environ)
322 request.rpc_ip_addr = get_ip_addr(request.environ)
323 # TODO(marcink): deprecate GET at some point
323 # TODO(marcink): deprecate GET at some point
324 if request.method not in ['POST', 'GET']:
324 if request.method not in ['POST', 'GET']:
325 log.debug('unsupported request method "%s"', request.method)
325 log.debug('unsupported request method "%s"', request.method)
326 raise JSONRPCError(
326 raise JSONRPCError(
327 'unsupported request method "%s". Please use POST' % request.method)
327 'unsupported request method "%s". Please use POST' % request.method)
328
328
329 if 'CONTENT_LENGTH' not in request.environ:
329 if 'CONTENT_LENGTH' not in request.environ:
330 log.debug("No Content-Length")
330 log.debug("No Content-Length")
331 raise JSONRPCError("Empty body, No Content-Length in request")
331 raise JSONRPCError("Empty body, No Content-Length in request")
332
332
333 else:
333 else:
334 length = request.environ['CONTENT_LENGTH']
334 length = request.environ['CONTENT_LENGTH']
335 log.debug('Content-Length: %s', length)
335 log.debug('Content-Length: %s', length)
336
336
337 if length == 0:
337 if length == 0:
338 log.debug("Content-Length is 0")
338 log.debug("Content-Length is 0")
339 raise JSONRPCError("Content-Length is 0")
339 raise JSONRPCError("Content-Length is 0")
340
340
341 raw_body = request.body
341 raw_body = request.body
342 log.debug("Loading JSON body now")
342 log.debug("Loading JSON body now")
343 try:
343 try:
344 json_body = json.loads(raw_body)
344 json_body = json.loads(raw_body)
345 except ValueError as e:
345 except ValueError as e:
346 # catch JSON errors Here
346 # catch JSON errors Here
347 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
347 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
348
348
349 request.rpc_id = json_body.get('id')
349 request.rpc_id = json_body.get('id')
350 request.rpc_method = json_body.get('method')
350 request.rpc_method = json_body.get('method')
351
351
352 # check required base parameters
352 # check required base parameters
353 try:
353 try:
354 api_key = json_body.get('api_key')
354 api_key = json_body.get('api_key')
355 if not api_key:
355 if not api_key:
356 api_key = json_body.get('auth_token')
356 api_key = json_body.get('auth_token')
357
357
358 if not api_key:
358 if not api_key:
359 raise KeyError('api_key or auth_token')
359 raise KeyError('api_key or auth_token')
360
360
361 # TODO(marcink): support passing in token in request header
361 # TODO(marcink): support passing in token in request header
362
362
363 request.rpc_api_key = api_key
363 request.rpc_api_key = api_key
364 request.rpc_id = json_body['id']
364 request.rpc_id = json_body['id']
365 request.rpc_method = json_body['method']
365 request.rpc_method = json_body['method']
366 request.rpc_params = json_body['args'] \
366 request.rpc_params = json_body['args'] \
367 if isinstance(json_body['args'], dict) else {}
367 if isinstance(json_body['args'], dict) else {}
368
368
369 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
369 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
370 except KeyError as e:
370 except KeyError as e:
371 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
371 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
372
372
373 log.debug('setup complete, now handling method:%s rpcid:%s',
373 log.debug('setup complete, now handling method:%s rpcid:%s',
374 request.rpc_method, request.rpc_id, )
374 request.rpc_method, request.rpc_id, )
375
375
376
376
377 class RoutePredicate(object):
377 class RoutePredicate(object):
378 def __init__(self, val, config):
378 def __init__(self, val, config):
379 self.val = val
379 self.val = val
380
380
381 def text(self):
381 def text(self):
382 return 'jsonrpc route = %s' % self.val
382 return 'jsonrpc route = %s' % self.val
383
383
384 phash = text
384 phash = text
385
385
386 def __call__(self, info, request):
386 def __call__(self, info, request):
387 if self.val:
387 if self.val:
388 # potentially setup and bootstrap our call
388 # potentially setup and bootstrap our call
389 setup_request(request)
389 setup_request(request)
390
390
391 # Always return True so that even if it isn't a valid RPC it
391 # Always return True so that even if it isn't a valid RPC it
392 # will fall through to the underlaying handlers like notfound_view
392 # will fall through to the underlaying handlers like notfound_view
393 return True
393 return True
394
394
395
395
396 class NotFoundPredicate(object):
396 class NotFoundPredicate(object):
397 def __init__(self, val, config):
397 def __init__(self, val, config):
398 self.val = val
398 self.val = val
399 self.methods = config.registry.jsonrpc_methods
399 self.methods = config.registry.jsonrpc_methods
400
400
401 def text(self):
401 def text(self):
402 return 'jsonrpc method not found = {}.'.format(self.val)
402 return 'jsonrpc method not found = {}.'.format(self.val)
403
403
404 phash = text
404 phash = text
405
405
406 def __call__(self, info, request):
406 def __call__(self, info, request):
407 return hasattr(request, 'rpc_method')
407 return hasattr(request, 'rpc_method')
408
408
409
409
410 class MethodPredicate(object):
410 class MethodPredicate(object):
411 def __init__(self, val, config):
411 def __init__(self, val, config):
412 self.method = val
412 self.method = val
413
413
414 def text(self):
414 def text(self):
415 return 'jsonrpc method = %s' % self.method
415 return 'jsonrpc method = %s' % self.method
416
416
417 phash = text
417 phash = text
418
418
419 def __call__(self, context, request):
419 def __call__(self, context, request):
420 # we need to explicitly return False here, so pyramid doesn't try to
420 # we need to explicitly return False here, so pyramid doesn't try to
421 # execute our view directly. We need our main handler to execute things
421 # execute our view directly. We need our main handler to execute things
422 return getattr(request, 'rpc_method') == self.method
422 return getattr(request, 'rpc_method') == self.method
423
423
424
424
425 def add_jsonrpc_method(config, view, **kwargs):
425 def add_jsonrpc_method(config, view, **kwargs):
426 # pop the method name
426 # pop the method name
427 method = kwargs.pop('method', None)
427 method = kwargs.pop('method', None)
428
428
429 if method is None:
429 if method is None:
430 raise ConfigurationError(
430 raise ConfigurationError(
431 'Cannot register a JSON-RPC method without specifying the "method"')
431 'Cannot register a JSON-RPC method without specifying the "method"')
432
432
433 # we define custom predicate, to enable to detect conflicting methods,
433 # we define custom predicate, to enable to detect conflicting methods,
434 # those predicates are kind of "translation" from the decorator variables
434 # those predicates are kind of "translation" from the decorator variables
435 # to internal predicates names
435 # to internal predicates names
436
436
437 kwargs['jsonrpc_method'] = method
437 kwargs['jsonrpc_method'] = method
438
438
439 # register our view into global view store for validation
439 # register our view into global view store for validation
440 config.registry.jsonrpc_methods[method] = view
440 config.registry.jsonrpc_methods[method] = view
441
441
442 # we're using our main request_view handler, here, so each method
442 # we're using our main request_view handler, here, so each method
443 # has a unified handler for itself
443 # has a unified handler for itself
444 config.add_view(request_view, route_name='apiv2', **kwargs)
444 config.add_view(request_view, route_name='apiv2', **kwargs)
445
445
446
446
447 class jsonrpc_method(object):
447 class jsonrpc_method(object):
448 """
448 """
449 decorator that works similar to @add_view_config decorator,
449 decorator that works similar to @add_view_config decorator,
450 but tailored for our JSON RPC
450 but tailored for our JSON RPC
451 """
451 """
452
452
453 venusian = venusian # for testing injection
453 venusian = venusian # for testing injection
454
454
455 def __init__(self, method=None, **kwargs):
455 def __init__(self, method=None, **kwargs):
456 self.method = method
456 self.method = method
457 self.kwargs = kwargs
457 self.kwargs = kwargs
458
458
459 def __call__(self, wrapped):
459 def __call__(self, wrapped):
460 kwargs = self.kwargs.copy()
460 kwargs = self.kwargs.copy()
461 kwargs['method'] = self.method or wrapped.__name__
461 kwargs['method'] = self.method or wrapped.__name__
462 depth = kwargs.pop('_depth', 0)
462 depth = kwargs.pop('_depth', 0)
463
463
464 def callback(context, name, ob):
464 def callback(context, name, ob):
465 config = context.config.with_package(info.module)
465 config = context.config.with_package(info.module)
466 config.add_jsonrpc_method(view=ob, **kwargs)
466 config.add_jsonrpc_method(view=ob, **kwargs)
467
467
468 info = venusian.attach(wrapped, callback, category='pyramid',
468 info = venusian.attach(wrapped, callback, category='pyramid',
469 depth=depth + 1)
469 depth=depth + 1)
470 if info.scope == 'class':
470 if info.scope == 'class':
471 # ensure that attr is set if decorating a class method
471 # ensure that attr is set if decorating a class method
472 kwargs.setdefault('attr', wrapped.__name__)
472 kwargs.setdefault('attr', wrapped.__name__)
473
473
474 kwargs['_info'] = info.codeinfo # fbo action_method
474 kwargs['_info'] = info.codeinfo # fbo action_method
475 return wrapped
475 return wrapped
476
476
477
477
478 class jsonrpc_deprecated_method(object):
478 class jsonrpc_deprecated_method(object):
479 """
479 """
480 Marks method as deprecated, adds log.warning, and inject special key to
480 Marks method as deprecated, adds log.warning, and inject special key to
481 the request variable to mark method as deprecated.
481 the request variable to mark method as deprecated.
482 Also injects special docstring that extract_docs will catch to mark
482 Also injects special docstring that extract_docs will catch to mark
483 method as deprecated.
483 method as deprecated.
484
484
485 :param use_method: specify which method should be used instead of
485 :param use_method: specify which method should be used instead of
486 the decorated one
486 the decorated one
487
487
488 Use like::
488 Use like::
489
489
490 @jsonrpc_method()
490 @jsonrpc_method()
491 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
491 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
492 def old_func(request, apiuser, arg1, arg2):
492 def old_func(request, apiuser, arg1, arg2):
493 ...
493 ...
494 """
494 """
495
495
496 def __init__(self, use_method, deprecated_at_version):
496 def __init__(self, use_method, deprecated_at_version):
497 self.use_method = use_method
497 self.use_method = use_method
498 self.deprecated_at_version = deprecated_at_version
498 self.deprecated_at_version = deprecated_at_version
499 self.deprecated_msg = ''
499 self.deprecated_msg = ''
500
500
501 def __call__(self, func):
501 def __call__(self, func):
502 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
502 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
503 method=self.use_method)
503 method=self.use_method)
504
504
505 docstring = """\n
505 docstring = """\n
506 .. deprecated:: {version}
506 .. deprecated:: {version}
507
507
508 {deprecation_message}
508 {deprecation_message}
509
509
510 {original_docstring}
510 {original_docstring}
511 """
511 """
512 func.__doc__ = docstring.format(
512 func.__doc__ = docstring.format(
513 version=self.deprecated_at_version,
513 version=self.deprecated_at_version,
514 deprecation_message=self.deprecated_msg,
514 deprecation_message=self.deprecated_msg,
515 original_docstring=func.__doc__)
515 original_docstring=func.__doc__)
516 return decorator.decorator(self.__wrapper, func)
516 return decorator.decorator(self.__wrapper, func)
517
517
518 def __wrapper(self, func, *fargs, **fkwargs):
518 def __wrapper(self, func, *fargs, **fkwargs):
519 log.warning('DEPRECATED API CALL on function %s, please '
519 log.warning('DEPRECATED API CALL on function %s, please '
520 'use `%s` instead', func, self.use_method)
520 'use `%s` instead', func, self.use_method)
521 # alter function docstring to mark as deprecated, this is picked up
521 # alter function docstring to mark as deprecated, this is picked up
522 # via fabric file that generates API DOC.
522 # via fabric file that generates API DOC.
523 result = func(*fargs, **fkwargs)
523 result = func(*fargs, **fkwargs)
524
524
525 request = fargs[0]
525 request = fargs[0]
526 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
526 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
527 return result
527 return result
528
528
529
529
530 def includeme(config):
530 def includeme(config):
531 plugin_module = 'rhodecode.api'
531 plugin_module = 'rhodecode.api'
532 plugin_settings = get_plugin_settings(
532 plugin_settings = get_plugin_settings(
533 plugin_module, config.registry.settings)
533 plugin_module, config.registry.settings)
534
534
535 if not hasattr(config.registry, 'jsonrpc_methods'):
535 if not hasattr(config.registry, 'jsonrpc_methods'):
536 config.registry.jsonrpc_methods = OrderedDict()
536 config.registry.jsonrpc_methods = OrderedDict()
537
537
538 # match filter by given method only
538 # match filter by given method only
539 config.add_view_predicate('jsonrpc_method', MethodPredicate)
539 config.add_view_predicate('jsonrpc_method', MethodPredicate)
540 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
540 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
541
541
542 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
542 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
543 serializer=json.dumps, indent=4))
543 serializer=json.dumps, indent=4))
544 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
544 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
545
545
546 config.add_route_predicate(
546 config.add_route_predicate(
547 'jsonrpc_call', RoutePredicate)
547 'jsonrpc_call', RoutePredicate)
548
548
549 config.add_route(
549 config.add_route(
550 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
550 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
551
551
552 config.scan(plugin_module, ignore='rhodecode.api.tests')
552 config.scan(plugin_module, ignore='rhodecode.api.tests')
553 # register some exception handling view
553 # register some exception handling view
554 config.add_view(exception_view, context=JSONRPCBaseError)
554 config.add_view(exception_view, context=JSONRPCBaseError)
555 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
555 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,42 +1,42 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 class JSONRPCBaseError(Exception):
22 class JSONRPCBaseError(Exception):
23 def __init__(self, message='', *args):
23 def __init__(self, message='', *args):
24 self.message = message
24 self.message = message
25 super(JSONRPCBaseError, self).__init__(message, *args)
25 super(JSONRPCBaseError, self).__init__(message, *args)
26
26
27
27
28 class JSONRPCError(JSONRPCBaseError):
28 class JSONRPCError(JSONRPCBaseError):
29 pass
29 pass
30
30
31
31
32 class JSONRPCValidationError(JSONRPCBaseError):
32 class JSONRPCValidationError(JSONRPCBaseError):
33
33
34 def __init__(self, *args, **kwargs):
34 def __init__(self, *args, **kwargs):
35 self.colander_exception = kwargs.pop('colander_exc')
35 self.colander_exception = kwargs.pop('colander_exc')
36 super(JSONRPCValidationError, self).__init__(
36 super(JSONRPCValidationError, self).__init__(
37 message=self.colander_exception, *args)
37 message=self.colander_exception, *args)
38
38
39
39
40 class JSONRPCForbidden(JSONRPCBaseError):
40 class JSONRPCForbidden(JSONRPCBaseError):
41 pass
41 pass
42
42
@@ -1,19 +1,19 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,52 +1,52 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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,133 +1,133 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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. ' \
89 expected = 'No such method: comment. ' \
90 'Similar methods: changeset_comment, comment_pull_request, ' \
90 'Similar methods: changeset_comment, comment_pull_request, ' \
91 'get_pull_request_comments, comment_commit, get_repo_comments'
91 'get_pull_request_comments, comment_commit, get_repo_comments'
92 assert_error(id_, expected, given=response.body)
92 assert_error(id_, expected, given=response.body)
93
93
94 def test_api_disabled_user(self, request):
94 def test_api_disabled_user(self, request):
95
95
96 def set_active(active):
96 def set_active(active):
97 from rhodecode.model.db import Session, User
97 from rhodecode.model.db import Session, User
98 user = User.get_by_auth_token(self.apikey)
98 user = User.get_by_auth_token(self.apikey)
99 user.active = active
99 user.active = active
100 Session().add(user)
100 Session().add(user)
101 Session().commit()
101 Session().commit()
102
102
103 request.addfinalizer(lambda: set_active(True))
103 request.addfinalizer(lambda: set_active(True))
104
104
105 set_active(False)
105 set_active(False)
106 id_, params = build_data(self.apikey, 'test', args='xx')
106 id_, params = build_data(self.apikey, 'test', args='xx')
107 response = api_call(self.app, params)
107 response = api_call(self.app, params)
108 expected = 'Request from this user not allowed'
108 expected = 'Request from this user not allowed'
109 assert_error(id_, expected, given=response.body)
109 assert_error(id_, expected, given=response.body)
110
110
111 def test_api_args_is_null(self):
111 def test_api_args_is_null(self):
112 __, params = build_data(self.apikey, 'get_users', )
112 __, params = build_data(self.apikey, 'get_users', )
113 params = params.replace('"args": {}', '"args": null')
113 params = params.replace('"args": {}', '"args": null')
114 response = api_call(self.app, params)
114 response = api_call(self.app, params)
115 assert response.status == '200 OK'
115 assert response.status == '200 OK'
116
116
117 def test_api_args_is_bad(self):
117 def test_api_args_is_bad(self):
118 __, params = build_data(self.apikey, 'get_users', )
118 __, params = build_data(self.apikey, 'get_users', )
119 params = params.replace('"args": {}', '"args": 1')
119 params = params.replace('"args": {}', '"args": 1')
120 response = api_call(self.app, params)
120 response = api_call(self.app, params)
121 assert response.status == '200 OK'
121 assert response.status == '200 OK'
122
122
123 def test_api_args_different_args(self):
123 def test_api_args_different_args(self):
124 import string
124 import string
125 expected = {
125 expected = {
126 'ascii_letters': string.ascii_letters,
126 'ascii_letters': string.ascii_letters,
127 'ws': string.whitespace,
127 'ws': string.whitespace,
128 'printables': string.printable
128 'printables': string.printable
129 }
129 }
130 id_, params = build_data(self.apikey, 'test', args=expected)
130 id_, params = build_data(self.apikey, 'test', args=expected)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132 assert response.status == '200 OK'
132 assert response.status == '200 OK'
133 assert_ok(id_, expected, response.body)
133 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-2019 RhodeCode GmbH
3 # Copyright (C) 2017-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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(UserLog.user_log_id.asc()) \
52 .order_by(UserLog.user_log_id.asc()) \
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,116 +1,116 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import ChangesetStatus, User
23 from rhodecode.model.db import ChangesetStatus, User
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().name, commit=commit_id)
56 repo=backend.repo.scm_instance().name, 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_commit(commit_id).raw_id
68 commit_id = backend.repo.scm_instance().get_commit(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_commit().raw_id, backend.repo_name),
77 repo.get_commit().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)
82
82
83 def test_api_comment_commit_with_extra_recipients(self, backend, user_util):
83 def test_api_comment_commit_with_extra_recipients(self, backend, user_util):
84
84
85 commit_id = backend.repo.scm_instance().get_commit('tip').raw_id
85 commit_id = backend.repo.scm_instance().get_commit('tip').raw_id
86
86
87 user1 = user_util.create_user()
87 user1 = user_util.create_user()
88 user1_id = user1.user_id
88 user1_id = user1.user_id
89 user2 = user_util.create_user()
89 user2 = user_util.create_user()
90 user2_id = user2.user_id
90 user2_id = user2.user_id
91
91
92 id_, params = build_data(
92 id_, params = build_data(
93 self.apikey, 'comment_commit', repoid=backend.repo_name,
93 self.apikey, 'comment_commit', repoid=backend.repo_name,
94 commit_id=commit_id,
94 commit_id=commit_id,
95 message='abracadabra',
95 message='abracadabra',
96 extra_recipients=[user1.user_id, user2.username])
96 extra_recipients=[user1.user_id, user2.username])
97
97
98 response = api_call(self.app, params)
98 response = api_call(self.app, params)
99 repo = backend.repo.scm_instance()
99 repo = backend.repo.scm_instance()
100
100
101 expected = {
101 expected = {
102 'msg': 'Commented on commit `%s` for repository `%s`' % (
102 'msg': 'Commented on commit `%s` for repository `%s`' % (
103 repo.get_commit().raw_id, backend.repo_name),
103 repo.get_commit().raw_id, backend.repo_name),
104 'status_change': None,
104 'status_change': None,
105 'success': True
105 'success': True
106 }
106 }
107
107
108 assert_ok(id_, expected, given=response.body)
108 assert_ok(id_, expected, given=response.body)
109 # check user1/user2 inbox for notification
109 # check user1/user2 inbox for notification
110 user1 = User.get(user1_id)
110 user1 = User.get(user1_id)
111 assert 1 == len(user1.notifications)
111 assert 1 == len(user1.notifications)
112 assert 'abracadabra' in user1.notifications[0].notification.body
112 assert 'abracadabra' in user1.notifications[0].notification.body
113
113
114 user2 = User.get(user2_id)
114 user2 = User.get(user2_id)
115 assert 1 == len(user2.notifications)
115 assert 1 == len(user2.notifications)
116 assert 'abracadabra' in user2.notifications[0].notification.body
116 assert 'abracadabra' in user2.notifications[0].notification.body
@@ -1,246 +1,246 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.comment import CommentsModel
23 from rhodecode.model.comment import CommentsModel
24 from rhodecode.model.db import UserLog, User
24 from rhodecode.model.db import UserLog, 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_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(UserLog.user_log_id.asc()) \
68 .order_by(UserLog.user_log_id.asc()) \
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_with_extra_recipients(self, pr_util, user_util):
73 def test_api_comment_pull_request_with_extra_recipients(self, pr_util, user_util):
74 pull_request = pr_util.create_pull_request()
74 pull_request = pr_util.create_pull_request()
75
75
76 user1 = user_util.create_user()
76 user1 = user_util.create_user()
77 user1_id = user1.user_id
77 user1_id = user1.user_id
78 user2 = user_util.create_user()
78 user2 = user_util.create_user()
79 user2_id = user2.user_id
79 user2_id = user2.user_id
80
80
81 id_, params = build_data(
81 id_, params = build_data(
82 self.apikey, 'comment_pull_request',
82 self.apikey, 'comment_pull_request',
83 repoid=pull_request.target_repo.repo_name,
83 repoid=pull_request.target_repo.repo_name,
84 pullrequestid=pull_request.pull_request_id,
84 pullrequestid=pull_request.pull_request_id,
85 message='test message',
85 message='test message',
86 extra_recipients=[user1.user_id, user2.username]
86 extra_recipients=[user1.user_id, user2.username]
87 )
87 )
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89 pull_request = PullRequestModel().get(pull_request.pull_request_id)
89 pull_request = PullRequestModel().get(pull_request.pull_request_id)
90
90
91 comments = CommentsModel().get_comments(
91 comments = CommentsModel().get_comments(
92 pull_request.target_repo.repo_id, pull_request=pull_request)
92 pull_request.target_repo.repo_id, pull_request=pull_request)
93
93
94 expected = {
94 expected = {
95 'pull_request_id': pull_request.pull_request_id,
95 'pull_request_id': pull_request.pull_request_id,
96 'comment_id': comments[-1].comment_id,
96 'comment_id': comments[-1].comment_id,
97 'status': {'given': None, 'was_changed': None}
97 'status': {'given': None, 'was_changed': None}
98 }
98 }
99 assert_ok(id_, expected, response.body)
99 assert_ok(id_, expected, response.body)
100 # check user1/user2 inbox for notification
100 # check user1/user2 inbox for notification
101 user1 = User.get(user1_id)
101 user1 = User.get(user1_id)
102 assert 1 == len(user1.notifications)
102 assert 1 == len(user1.notifications)
103 assert 'test message' in user1.notifications[0].notification.body
103 assert 'test message' in user1.notifications[0].notification.body
104
104
105 user2 = User.get(user2_id)
105 user2 = User.get(user2_id)
106 assert 1 == len(user2.notifications)
106 assert 1 == len(user2.notifications)
107 assert 'test message' in user2.notifications[0].notification.body
107 assert 'test message' in user2.notifications[0].notification.body
108
108
109 @pytest.mark.backends("git", "hg")
109 @pytest.mark.backends("git", "hg")
110 def test_api_comment_pull_request_change_status(
110 def test_api_comment_pull_request_change_status(
111 self, pr_util, no_notifications):
111 self, pr_util, no_notifications):
112 pull_request = pr_util.create_pull_request()
112 pull_request = pr_util.create_pull_request()
113 pull_request_id = pull_request.pull_request_id
113 pull_request_id = pull_request.pull_request_id
114 id_, params = build_data(
114 id_, params = build_data(
115 self.apikey, 'comment_pull_request',
115 self.apikey, 'comment_pull_request',
116 repoid=pull_request.target_repo.repo_name,
116 repoid=pull_request.target_repo.repo_name,
117 pullrequestid=pull_request.pull_request_id,
117 pullrequestid=pull_request.pull_request_id,
118 status='rejected')
118 status='rejected')
119 response = api_call(self.app, params)
119 response = api_call(self.app, params)
120 pull_request = PullRequestModel().get(pull_request_id)
120 pull_request = PullRequestModel().get(pull_request_id)
121
121
122 comments = CommentsModel().get_comments(
122 comments = CommentsModel().get_comments(
123 pull_request.target_repo.repo_id, pull_request=pull_request)
123 pull_request.target_repo.repo_id, pull_request=pull_request)
124 expected = {
124 expected = {
125 'pull_request_id': pull_request.pull_request_id,
125 'pull_request_id': pull_request.pull_request_id,
126 'comment_id': comments[-1].comment_id,
126 'comment_id': comments[-1].comment_id,
127 'status': {'given': 'rejected', 'was_changed': True}
127 'status': {'given': 'rejected', 'was_changed': True}
128 }
128 }
129 assert_ok(id_, expected, response.body)
129 assert_ok(id_, expected, response.body)
130
130
131 @pytest.mark.backends("git", "hg")
131 @pytest.mark.backends("git", "hg")
132 def test_api_comment_pull_request_change_status_with_specific_commit_id(
132 def test_api_comment_pull_request_change_status_with_specific_commit_id(
133 self, pr_util, no_notifications):
133 self, pr_util, no_notifications):
134 pull_request = pr_util.create_pull_request()
134 pull_request = pr_util.create_pull_request()
135 pull_request_id = pull_request.pull_request_id
135 pull_request_id = pull_request.pull_request_id
136 latest_commit_id = 'test_commit'
136 latest_commit_id = 'test_commit'
137 # inject additional revision, to fail test the status change on
137 # inject additional revision, to fail test the status change on
138 # non-latest commit
138 # non-latest commit
139 pull_request.revisions = pull_request.revisions + ['test_commit']
139 pull_request.revisions = pull_request.revisions + ['test_commit']
140
140
141 id_, params = build_data(
141 id_, params = build_data(
142 self.apikey, 'comment_pull_request',
142 self.apikey, 'comment_pull_request',
143 repoid=pull_request.target_repo.repo_name,
143 repoid=pull_request.target_repo.repo_name,
144 pullrequestid=pull_request.pull_request_id,
144 pullrequestid=pull_request.pull_request_id,
145 status='approved', commit_id=latest_commit_id)
145 status='approved', commit_id=latest_commit_id)
146 response = api_call(self.app, params)
146 response = api_call(self.app, params)
147 pull_request = PullRequestModel().get(pull_request_id)
147 pull_request = PullRequestModel().get(pull_request_id)
148
148
149 expected = {
149 expected = {
150 'pull_request_id': pull_request.pull_request_id,
150 'pull_request_id': pull_request.pull_request_id,
151 'comment_id': None,
151 'comment_id': None,
152 'status': {'given': 'approved', 'was_changed': False}
152 'status': {'given': 'approved', 'was_changed': False}
153 }
153 }
154 assert_ok(id_, expected, response.body)
154 assert_ok(id_, expected, response.body)
155
155
156 @pytest.mark.backends("git", "hg")
156 @pytest.mark.backends("git", "hg")
157 def test_api_comment_pull_request_change_status_with_specific_commit_id(
157 def test_api_comment_pull_request_change_status_with_specific_commit_id(
158 self, pr_util, no_notifications):
158 self, pr_util, no_notifications):
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 latest_commit_id = pull_request.revisions[0]
161 latest_commit_id = pull_request.revisions[0]
162
162
163 id_, params = build_data(
163 id_, params = build_data(
164 self.apikey, 'comment_pull_request',
164 self.apikey, 'comment_pull_request',
165 repoid=pull_request.target_repo.repo_name,
165 repoid=pull_request.target_repo.repo_name,
166 pullrequestid=pull_request.pull_request_id,
166 pullrequestid=pull_request.pull_request_id,
167 status='approved', commit_id=latest_commit_id)
167 status='approved', commit_id=latest_commit_id)
168 response = api_call(self.app, params)
168 response = api_call(self.app, params)
169 pull_request = PullRequestModel().get(pull_request_id)
169 pull_request = PullRequestModel().get(pull_request_id)
170
170
171 comments = CommentsModel().get_comments(
171 comments = CommentsModel().get_comments(
172 pull_request.target_repo.repo_id, pull_request=pull_request)
172 pull_request.target_repo.repo_id, pull_request=pull_request)
173 expected = {
173 expected = {
174 'pull_request_id': pull_request.pull_request_id,
174 'pull_request_id': pull_request.pull_request_id,
175 'comment_id': comments[-1].comment_id,
175 'comment_id': comments[-1].comment_id,
176 'status': {'given': 'approved', 'was_changed': True}
176 'status': {'given': 'approved', 'was_changed': True}
177 }
177 }
178 assert_ok(id_, expected, response.body)
178 assert_ok(id_, expected, response.body)
179
179
180 @pytest.mark.backends("git", "hg")
180 @pytest.mark.backends("git", "hg")
181 def test_api_comment_pull_request_missing_params_error(self, pr_util):
181 def test_api_comment_pull_request_missing_params_error(self, pr_util):
182 pull_request = pr_util.create_pull_request()
182 pull_request = pr_util.create_pull_request()
183 pull_request_id = pull_request.pull_request_id
183 pull_request_id = pull_request.pull_request_id
184 pull_request_repo = pull_request.target_repo.repo_name
184 pull_request_repo = pull_request.target_repo.repo_name
185 id_, params = build_data(
185 id_, params = build_data(
186 self.apikey, 'comment_pull_request',
186 self.apikey, 'comment_pull_request',
187 repoid=pull_request_repo,
187 repoid=pull_request_repo,
188 pullrequestid=pull_request_id)
188 pullrequestid=pull_request_id)
189 response = api_call(self.app, params)
189 response = api_call(self.app, params)
190
190
191 expected = 'Both message and status parameters are missing. At least one is required.'
191 expected = 'Both message and status parameters are missing. At least one is required.'
192 assert_error(id_, expected, given=response.body)
192 assert_error(id_, expected, given=response.body)
193
193
194 @pytest.mark.backends("git", "hg")
194 @pytest.mark.backends("git", "hg")
195 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
195 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
196 pull_request = pr_util.create_pull_request()
196 pull_request = pr_util.create_pull_request()
197 pull_request_id = pull_request.pull_request_id
197 pull_request_id = pull_request.pull_request_id
198 pull_request_repo = pull_request.target_repo.repo_name
198 pull_request_repo = pull_request.target_repo.repo_name
199 id_, params = build_data(
199 id_, params = build_data(
200 self.apikey, 'comment_pull_request',
200 self.apikey, 'comment_pull_request',
201 repoid=pull_request_repo,
201 repoid=pull_request_repo,
202 pullrequestid=pull_request_id,
202 pullrequestid=pull_request_id,
203 status='42')
203 status='42')
204 response = api_call(self.app, params)
204 response = api_call(self.app, params)
205
205
206 expected = 'Unknown comment status: `42`'
206 expected = 'Unknown comment status: `42`'
207 assert_error(id_, expected, given=response.body)
207 assert_error(id_, expected, given=response.body)
208
208
209 @pytest.mark.backends("git", "hg")
209 @pytest.mark.backends("git", "hg")
210 def test_api_comment_pull_request_repo_error(self, pr_util):
210 def test_api_comment_pull_request_repo_error(self, pr_util):
211 pull_request = pr_util.create_pull_request()
211 pull_request = pr_util.create_pull_request()
212 id_, params = build_data(
212 id_, params = build_data(
213 self.apikey, 'comment_pull_request',
213 self.apikey, 'comment_pull_request',
214 repoid=666, pullrequestid=pull_request.pull_request_id)
214 repoid=666, pullrequestid=pull_request.pull_request_id)
215 response = api_call(self.app, params)
215 response = api_call(self.app, params)
216
216
217 expected = 'repository `666` does not exist'
217 expected = 'repository `666` does not exist'
218 assert_error(id_, expected, given=response.body)
218 assert_error(id_, expected, given=response.body)
219
219
220 @pytest.mark.backends("git", "hg")
220 @pytest.mark.backends("git", "hg")
221 def test_api_comment_pull_request_non_admin_with_userid_error(
221 def test_api_comment_pull_request_non_admin_with_userid_error(
222 self, pr_util):
222 self, pr_util):
223 pull_request = pr_util.create_pull_request()
223 pull_request = pr_util.create_pull_request()
224 id_, params = build_data(
224 id_, params = build_data(
225 self.apikey_regular, 'comment_pull_request',
225 self.apikey_regular, 'comment_pull_request',
226 repoid=pull_request.target_repo.repo_name,
226 repoid=pull_request.target_repo.repo_name,
227 pullrequestid=pull_request.pull_request_id,
227 pullrequestid=pull_request.pull_request_id,
228 userid=TEST_USER_ADMIN_LOGIN)
228 userid=TEST_USER_ADMIN_LOGIN)
229 response = api_call(self.app, params)
229 response = api_call(self.app, params)
230
230
231 expected = 'userid is not the same as your user'
231 expected = 'userid is not the same as your user'
232 assert_error(id_, expected, given=response.body)
232 assert_error(id_, expected, given=response.body)
233
233
234 @pytest.mark.backends("git", "hg")
234 @pytest.mark.backends("git", "hg")
235 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
235 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
236 pull_request = pr_util.create_pull_request()
236 pull_request = pr_util.create_pull_request()
237 id_, params = build_data(
237 id_, params = build_data(
238 self.apikey_regular, 'comment_pull_request',
238 self.apikey_regular, 'comment_pull_request',
239 repoid=pull_request.target_repo.repo_name,
239 repoid=pull_request.target_repo.repo_name,
240 status='approved',
240 status='approved',
241 pullrequestid=pull_request.pull_request_id,
241 pullrequestid=pull_request.pull_request_id,
242 commit_id='XXX')
242 commit_id='XXX')
243 response = api_call(self.app, params)
243 response = api_call(self.app, params)
244
244
245 expected = 'Invalid commit_id `XXX` for this pull request.'
245 expected = 'Invalid commit_id `XXX` for this pull request.'
246 assert_error(id_, expected, given=response.body)
246 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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,368 +1,368 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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 }
47 }
48 for key in required_data:
48 for key in required_data:
49 data = required_data.copy()
49 data = required_data.copy()
50 data.pop(key)
50 data.pop(key)
51 id_, params = build_data(
51 id_, params = build_data(
52 self.apikey, 'create_pull_request', **data)
52 self.apikey, 'create_pull_request', **data)
53 response = api_call(self.app, params)
53 response = api_call(self.app, params)
54
54
55 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
55 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
56 assert_error(id_, expected, given=response.body)
56 assert_error(id_, expected, given=response.body)
57
57
58 @pytest.mark.backends("git", "hg")
58 @pytest.mark.backends("git", "hg")
59 @pytest.mark.parametrize('source_ref', [
59 @pytest.mark.parametrize('source_ref', [
60 'bookmarg:default:initial'
60 'bookmarg:default:initial'
61 ])
61 ])
62 def test_create_with_wrong_refs_data(self, backend, source_ref):
62 def test_create_with_wrong_refs_data(self, backend, source_ref):
63
63
64 data = self._prepare_data(backend)
64 data = self._prepare_data(backend)
65 data['source_ref'] = source_ref
65 data['source_ref'] = source_ref
66
66
67 id_, params = build_data(
67 id_, params = build_data(
68 self.apikey_regular, 'create_pull_request', **data)
68 self.apikey_regular, 'create_pull_request', **data)
69
69
70 response = api_call(self.app, params)
70 response = api_call(self.app, params)
71
71
72 expected = "Ref `{}` type is not allowed. " \
72 expected = "Ref `{}` type is not allowed. " \
73 "Only:['bookmark', 'book', 'tag', 'branch'] " \
73 "Only:['bookmark', 'book', 'tag', 'branch'] " \
74 "are possible.".format(source_ref)
74 "are possible.".format(source_ref)
75 assert_error(id_, expected, given=response.body)
75 assert_error(id_, expected, given=response.body)
76
76
77 @pytest.mark.backends("git", "hg")
77 @pytest.mark.backends("git", "hg")
78 def test_create_with_correct_data(self, backend):
78 def test_create_with_correct_data(self, backend):
79 data = self._prepare_data(backend)
79 data = self._prepare_data(backend)
80 RepoModel().revoke_user_permission(
80 RepoModel().revoke_user_permission(
81 self.source.repo_name, User.DEFAULT_USER)
81 self.source.repo_name, User.DEFAULT_USER)
82 id_, params = build_data(
82 id_, params = build_data(
83 self.apikey_regular, 'create_pull_request', **data)
83 self.apikey_regular, 'create_pull_request', **data)
84 response = api_call(self.app, params)
84 response = api_call(self.app, params)
85 expected_message = "Created new pull request `{title}`".format(
85 expected_message = "Created new pull request `{title}`".format(
86 title=data['title'])
86 title=data['title'])
87 result = response.json
87 result = response.json
88 assert result['error'] is None
88 assert result['error'] is None
89 assert result['result']['msg'] == expected_message
89 assert result['result']['msg'] == expected_message
90 pull_request_id = result['result']['pull_request_id']
90 pull_request_id = result['result']['pull_request_id']
91 pull_request = PullRequestModel().get(pull_request_id)
91 pull_request = PullRequestModel().get(pull_request_id)
92 assert pull_request.title == data['title']
92 assert pull_request.title == data['title']
93 assert pull_request.description == data['description']
93 assert pull_request.description == data['description']
94 assert pull_request.source_ref == data['source_ref']
94 assert pull_request.source_ref == data['source_ref']
95 assert pull_request.target_ref == data['target_ref']
95 assert pull_request.target_ref == data['target_ref']
96 assert pull_request.source_repo.repo_name == data['source_repo']
96 assert pull_request.source_repo.repo_name == data['source_repo']
97 assert pull_request.target_repo.repo_name == data['target_repo']
97 assert pull_request.target_repo.repo_name == data['target_repo']
98 assert pull_request.revisions == [self.commit_ids['change']]
98 assert pull_request.revisions == [self.commit_ids['change']]
99 assert len(pull_request.reviewers) == 1
99 assert len(pull_request.reviewers) == 1
100
100
101 @pytest.mark.backends("git", "hg")
101 @pytest.mark.backends("git", "hg")
102 def test_create_with_empty_description(self, backend):
102 def test_create_with_empty_description(self, backend):
103 data = self._prepare_data(backend)
103 data = self._prepare_data(backend)
104 data.pop('description')
104 data.pop('description')
105 id_, params = build_data(
105 id_, params = build_data(
106 self.apikey_regular, 'create_pull_request', **data)
106 self.apikey_regular, 'create_pull_request', **data)
107 response = api_call(self.app, params)
107 response = api_call(self.app, params)
108 expected_message = "Created new pull request `{title}`".format(
108 expected_message = "Created new pull request `{title}`".format(
109 title=data['title'])
109 title=data['title'])
110 result = response.json
110 result = response.json
111 assert result['error'] is None
111 assert result['error'] is None
112 assert result['result']['msg'] == expected_message
112 assert result['result']['msg'] == expected_message
113 pull_request_id = result['result']['pull_request_id']
113 pull_request_id = result['result']['pull_request_id']
114 pull_request = PullRequestModel().get(pull_request_id)
114 pull_request = PullRequestModel().get(pull_request_id)
115 assert pull_request.description == ''
115 assert pull_request.description == ''
116
116
117 @pytest.mark.backends("git", "hg")
117 @pytest.mark.backends("git", "hg")
118 def test_create_with_empty_title(self, backend):
118 def test_create_with_empty_title(self, backend):
119 data = self._prepare_data(backend)
119 data = self._prepare_data(backend)
120 data.pop('title')
120 data.pop('title')
121 id_, params = build_data(
121 id_, params = build_data(
122 self.apikey_regular, 'create_pull_request', **data)
122 self.apikey_regular, 'create_pull_request', **data)
123 response = api_call(self.app, params)
123 response = api_call(self.app, params)
124 result = response.json
124 result = response.json
125 pull_request_id = result['result']['pull_request_id']
125 pull_request_id = result['result']['pull_request_id']
126 pull_request = PullRequestModel().get(pull_request_id)
126 pull_request = PullRequestModel().get(pull_request_id)
127 data['ref'] = backend.default_branch_name
127 data['ref'] = backend.default_branch_name
128 title = '{source_repo}#{ref} to {target_repo}'.format(**data)
128 title = '{source_repo}#{ref} to {target_repo}'.format(**data)
129 assert pull_request.title == title
129 assert pull_request.title == title
130
130
131 @pytest.mark.backends("git", "hg")
131 @pytest.mark.backends("git", "hg")
132 def test_create_with_reviewers_specified_by_names(
132 def test_create_with_reviewers_specified_by_names(
133 self, backend, no_notifications):
133 self, backend, no_notifications):
134 data = self._prepare_data(backend)
134 data = self._prepare_data(backend)
135 reviewers = [
135 reviewers = [
136 {'username': TEST_USER_REGULAR_LOGIN,
136 {'username': TEST_USER_REGULAR_LOGIN,
137 'reasons': ['{} added manually'.format(TEST_USER_REGULAR_LOGIN)]},
137 'reasons': ['{} added manually'.format(TEST_USER_REGULAR_LOGIN)]},
138 {'username': TEST_USER_ADMIN_LOGIN,
138 {'username': TEST_USER_ADMIN_LOGIN,
139 'reasons': ['{} added manually'.format(TEST_USER_ADMIN_LOGIN)],
139 'reasons': ['{} added manually'.format(TEST_USER_ADMIN_LOGIN)],
140 'mandatory': True},
140 'mandatory': True},
141 ]
141 ]
142 data['reviewers'] = reviewers
142 data['reviewers'] = reviewers
143
143
144 id_, params = build_data(
144 id_, params = build_data(
145 self.apikey_regular, 'create_pull_request', **data)
145 self.apikey_regular, 'create_pull_request', **data)
146 response = api_call(self.app, params)
146 response = api_call(self.app, params)
147
147
148 expected_message = "Created new pull request `{title}`".format(
148 expected_message = "Created new pull request `{title}`".format(
149 title=data['title'])
149 title=data['title'])
150 result = response.json
150 result = response.json
151 assert result['error'] is None
151 assert result['error'] is None
152 assert result['result']['msg'] == expected_message
152 assert result['result']['msg'] == expected_message
153 pull_request_id = result['result']['pull_request_id']
153 pull_request_id = result['result']['pull_request_id']
154 pull_request = PullRequestModel().get(pull_request_id)
154 pull_request = PullRequestModel().get(pull_request_id)
155
155
156 actual_reviewers = []
156 actual_reviewers = []
157 for rev in pull_request.reviewers:
157 for rev in pull_request.reviewers:
158 entry = {
158 entry = {
159 'username': rev.user.username,
159 'username': rev.user.username,
160 'reasons': rev.reasons,
160 'reasons': rev.reasons,
161 }
161 }
162 if rev.mandatory:
162 if rev.mandatory:
163 entry['mandatory'] = rev.mandatory
163 entry['mandatory'] = rev.mandatory
164 actual_reviewers.append(entry)
164 actual_reviewers.append(entry)
165
165
166 owner_username = pull_request.target_repo.user.username
166 owner_username = pull_request.target_repo.user.username
167 for spec_reviewer in reviewers[::]:
167 for spec_reviewer in reviewers[::]:
168 # default reviewer will be added who is an owner of the repo
168 # default reviewer will be added who is an owner of the repo
169 # this get's overridden by a add owner to reviewers rule
169 # this get's overridden by a add owner to reviewers rule
170 if spec_reviewer['username'] == owner_username:
170 if spec_reviewer['username'] == owner_username:
171 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
171 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
172 # since owner is more important, we don't inherit mandatory flag
172 # since owner is more important, we don't inherit mandatory flag
173 del spec_reviewer['mandatory']
173 del spec_reviewer['mandatory']
174
174
175 assert sorted(actual_reviewers, key=lambda e: e['username']) \
175 assert sorted(actual_reviewers, key=lambda e: e['username']) \
176 == sorted(reviewers, key=lambda e: e['username'])
176 == sorted(reviewers, key=lambda e: e['username'])
177
177
178 @pytest.mark.backends("git", "hg")
178 @pytest.mark.backends("git", "hg")
179 def test_create_with_reviewers_specified_by_ids(
179 def test_create_with_reviewers_specified_by_ids(
180 self, backend, no_notifications):
180 self, backend, no_notifications):
181 data = self._prepare_data(backend)
181 data = self._prepare_data(backend)
182 reviewers = [
182 reviewers = [
183 {'username': UserModel().get_by_username(
183 {'username': UserModel().get_by_username(
184 TEST_USER_REGULAR_LOGIN).user_id,
184 TEST_USER_REGULAR_LOGIN).user_id,
185 'reasons': ['added manually']},
185 'reasons': ['added manually']},
186 {'username': UserModel().get_by_username(
186 {'username': UserModel().get_by_username(
187 TEST_USER_ADMIN_LOGIN).user_id,
187 TEST_USER_ADMIN_LOGIN).user_id,
188 'reasons': ['added manually']},
188 'reasons': ['added manually']},
189 ]
189 ]
190
190
191 data['reviewers'] = reviewers
191 data['reviewers'] = reviewers
192 id_, params = build_data(
192 id_, params = build_data(
193 self.apikey_regular, 'create_pull_request', **data)
193 self.apikey_regular, 'create_pull_request', **data)
194 response = api_call(self.app, params)
194 response = api_call(self.app, params)
195
195
196 expected_message = "Created new pull request `{title}`".format(
196 expected_message = "Created new pull request `{title}`".format(
197 title=data['title'])
197 title=data['title'])
198 result = response.json
198 result = response.json
199 assert result['error'] is None
199 assert result['error'] is None
200 assert result['result']['msg'] == expected_message
200 assert result['result']['msg'] == expected_message
201 pull_request_id = result['result']['pull_request_id']
201 pull_request_id = result['result']['pull_request_id']
202 pull_request = PullRequestModel().get(pull_request_id)
202 pull_request = PullRequestModel().get(pull_request_id)
203
203
204 actual_reviewers = []
204 actual_reviewers = []
205 for rev in pull_request.reviewers:
205 for rev in pull_request.reviewers:
206 entry = {
206 entry = {
207 'username': rev.user.user_id,
207 'username': rev.user.user_id,
208 'reasons': rev.reasons,
208 'reasons': rev.reasons,
209 }
209 }
210 if rev.mandatory:
210 if rev.mandatory:
211 entry['mandatory'] = rev.mandatory
211 entry['mandatory'] = rev.mandatory
212 actual_reviewers.append(entry)
212 actual_reviewers.append(entry)
213
213
214 owner_user_id = pull_request.target_repo.user.user_id
214 owner_user_id = pull_request.target_repo.user.user_id
215 for spec_reviewer in reviewers[::]:
215 for spec_reviewer in reviewers[::]:
216 # default reviewer will be added who is an owner of the repo
216 # default reviewer will be added who is an owner of the repo
217 # this get's overridden by a add owner to reviewers rule
217 # this get's overridden by a add owner to reviewers rule
218 if spec_reviewer['username'] == owner_user_id:
218 if spec_reviewer['username'] == owner_user_id:
219 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
219 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
220
220
221 assert sorted(actual_reviewers, key=lambda e: e['username']) \
221 assert sorted(actual_reviewers, key=lambda e: e['username']) \
222 == sorted(reviewers, key=lambda e: e['username'])
222 == sorted(reviewers, key=lambda e: e['username'])
223
223
224 @pytest.mark.backends("git", "hg")
224 @pytest.mark.backends("git", "hg")
225 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
225 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
226 data = self._prepare_data(backend)
226 data = self._prepare_data(backend)
227 data['reviewers'] = [{'username': 'somebody'}]
227 data['reviewers'] = [{'username': 'somebody'}]
228 id_, params = build_data(
228 id_, params = build_data(
229 self.apikey_regular, 'create_pull_request', **data)
229 self.apikey_regular, 'create_pull_request', **data)
230 response = api_call(self.app, params)
230 response = api_call(self.app, params)
231 expected_message = 'user `somebody` does not exist'
231 expected_message = 'user `somebody` does not exist'
232 assert_error(id_, expected_message, given=response.body)
232 assert_error(id_, expected_message, given=response.body)
233
233
234 @pytest.mark.backends("git", "hg")
234 @pytest.mark.backends("git", "hg")
235 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
235 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
236 data = self._prepare_data(backend)
236 data = self._prepare_data(backend)
237 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
237 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
238 data['reviewers'] = reviewers
238 data['reviewers'] = reviewers
239 id_, params = build_data(
239 id_, params = build_data(
240 self.apikey_regular, 'create_pull_request', **data)
240 self.apikey_regular, 'create_pull_request', **data)
241 response = api_call(self.app, params)
241 response = api_call(self.app, params)
242 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
242 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
243 assert_error(id_, expected_message, given=response.body)
243 assert_error(id_, expected_message, given=response.body)
244
244
245 @pytest.mark.backends("git", "hg")
245 @pytest.mark.backends("git", "hg")
246 def test_create_with_no_commit_hashes(self, backend):
246 def test_create_with_no_commit_hashes(self, backend):
247 data = self._prepare_data(backend)
247 data = self._prepare_data(backend)
248 expected_source_ref = data['source_ref']
248 expected_source_ref = data['source_ref']
249 expected_target_ref = data['target_ref']
249 expected_target_ref = data['target_ref']
250 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
250 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
251 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
251 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
252 id_, params = build_data(
252 id_, params = build_data(
253 self.apikey_regular, 'create_pull_request', **data)
253 self.apikey_regular, 'create_pull_request', **data)
254 response = api_call(self.app, params)
254 response = api_call(self.app, params)
255 expected_message = "Created new pull request `{title}`".format(
255 expected_message = "Created new pull request `{title}`".format(
256 title=data['title'])
256 title=data['title'])
257 result = response.json
257 result = response.json
258 assert result['result']['msg'] == expected_message
258 assert result['result']['msg'] == expected_message
259 pull_request_id = result['result']['pull_request_id']
259 pull_request_id = result['result']['pull_request_id']
260 pull_request = PullRequestModel().get(pull_request_id)
260 pull_request = PullRequestModel().get(pull_request_id)
261 assert pull_request.source_ref == expected_source_ref
261 assert pull_request.source_ref == expected_source_ref
262 assert pull_request.target_ref == expected_target_ref
262 assert pull_request.target_ref == expected_target_ref
263
263
264 @pytest.mark.backends("git", "hg")
264 @pytest.mark.backends("git", "hg")
265 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
265 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
266 def test_create_fails_with_wrong_repo(self, backend, data_key):
266 def test_create_fails_with_wrong_repo(self, backend, data_key):
267 repo_name = 'fake-repo'
267 repo_name = 'fake-repo'
268 data = self._prepare_data(backend)
268 data = self._prepare_data(backend)
269 data[data_key] = repo_name
269 data[data_key] = repo_name
270 id_, params = build_data(
270 id_, params = build_data(
271 self.apikey_regular, 'create_pull_request', **data)
271 self.apikey_regular, 'create_pull_request', **data)
272 response = api_call(self.app, params)
272 response = api_call(self.app, params)
273 expected_message = 'repository `{}` does not exist'.format(repo_name)
273 expected_message = 'repository `{}` does not exist'.format(repo_name)
274 assert_error(id_, expected_message, given=response.body)
274 assert_error(id_, expected_message, given=response.body)
275
275
276 @pytest.mark.backends("git", "hg")
276 @pytest.mark.backends("git", "hg")
277 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
277 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
278 def test_create_fails_with_non_existing_branch(self, backend, data_key):
278 def test_create_fails_with_non_existing_branch(self, backend, data_key):
279 branch_name = 'test-branch'
279 branch_name = 'test-branch'
280 data = self._prepare_data(backend)
280 data = self._prepare_data(backend)
281 data[data_key] = "branch:{}".format(branch_name)
281 data[data_key] = "branch:{}".format(branch_name)
282 id_, params = build_data(
282 id_, params = build_data(
283 self.apikey_regular, 'create_pull_request', **data)
283 self.apikey_regular, 'create_pull_request', **data)
284 response = api_call(self.app, params)
284 response = api_call(self.app, params)
285 expected_message = 'The specified value:{type}:`{name}` ' \
285 expected_message = 'The specified value:{type}:`{name}` ' \
286 'does not exist, or is not allowed.'.format(type='branch',
286 'does not exist, or is not allowed.'.format(type='branch',
287 name=branch_name)
287 name=branch_name)
288 assert_error(id_, expected_message, given=response.body)
288 assert_error(id_, expected_message, given=response.body)
289
289
290 @pytest.mark.backends("git", "hg")
290 @pytest.mark.backends("git", "hg")
291 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
291 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
292 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
292 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
293 data = self._prepare_data(backend)
293 data = self._prepare_data(backend)
294 ref = 'stange-ref'
294 ref = 'stange-ref'
295 data[data_key] = ref
295 data[data_key] = ref
296 id_, params = build_data(
296 id_, params = build_data(
297 self.apikey_regular, 'create_pull_request', **data)
297 self.apikey_regular, 'create_pull_request', **data)
298 response = api_call(self.app, params)
298 response = api_call(self.app, params)
299 expected_message = (
299 expected_message = (
300 'Ref `{ref}` given in a wrong format. Please check the API'
300 'Ref `{ref}` given in a wrong format. Please check the API'
301 ' documentation for more details'.format(ref=ref))
301 ' documentation for more details'.format(ref=ref))
302 assert_error(id_, expected_message, given=response.body)
302 assert_error(id_, expected_message, given=response.body)
303
303
304 @pytest.mark.backends("git", "hg")
304 @pytest.mark.backends("git", "hg")
305 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
305 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
306 def test_create_fails_with_non_existing_ref(self, backend, data_key):
306 def test_create_fails_with_non_existing_ref(self, backend, data_key):
307 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
307 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
308 ref = self._get_full_ref(backend, commit_id)
308 ref = self._get_full_ref(backend, commit_id)
309 data = self._prepare_data(backend)
309 data = self._prepare_data(backend)
310 data[data_key] = ref
310 data[data_key] = ref
311 id_, params = build_data(
311 id_, params = build_data(
312 self.apikey_regular, 'create_pull_request', **data)
312 self.apikey_regular, 'create_pull_request', **data)
313 response = api_call(self.app, params)
313 response = api_call(self.app, params)
314 expected_message = 'Ref `{}` does not exist'.format(ref)
314 expected_message = 'Ref `{}` does not exist'.format(ref)
315 assert_error(id_, expected_message, given=response.body)
315 assert_error(id_, expected_message, given=response.body)
316
316
317 @pytest.mark.backends("git", "hg")
317 @pytest.mark.backends("git", "hg")
318 def test_create_fails_when_no_revisions(self, backend):
318 def test_create_fails_when_no_revisions(self, backend):
319 data = self._prepare_data(backend, source_head='initial')
319 data = self._prepare_data(backend, source_head='initial')
320 id_, params = build_data(
320 id_, params = build_data(
321 self.apikey_regular, 'create_pull_request', **data)
321 self.apikey_regular, 'create_pull_request', **data)
322 response = api_call(self.app, params)
322 response = api_call(self.app, params)
323 expected_message = 'no commits found'
323 expected_message = 'no commits found'
324 assert_error(id_, expected_message, given=response.body)
324 assert_error(id_, expected_message, given=response.body)
325
325
326 @pytest.mark.backends("git", "hg")
326 @pytest.mark.backends("git", "hg")
327 def test_create_fails_when_no_permissions(self, backend):
327 def test_create_fails_when_no_permissions(self, backend):
328 data = self._prepare_data(backend)
328 data = self._prepare_data(backend)
329 RepoModel().revoke_user_permission(
329 RepoModel().revoke_user_permission(
330 self.source.repo_name, self.test_user)
330 self.source.repo_name, self.test_user)
331 RepoModel().revoke_user_permission(
331 RepoModel().revoke_user_permission(
332 self.source.repo_name, User.DEFAULT_USER)
332 self.source.repo_name, User.DEFAULT_USER)
333
333
334 id_, params = build_data(
334 id_, params = build_data(
335 self.apikey_regular, 'create_pull_request', **data)
335 self.apikey_regular, 'create_pull_request', **data)
336 response = api_call(self.app, params)
336 response = api_call(self.app, params)
337 expected_message = 'repository `{}` does not exist'.format(
337 expected_message = 'repository `{}` does not exist'.format(
338 self.source.repo_name)
338 self.source.repo_name)
339 assert_error(id_, expected_message, given=response.body)
339 assert_error(id_, expected_message, given=response.body)
340
340
341 def _prepare_data(
341 def _prepare_data(
342 self, backend, source_head='change', target_head='initial'):
342 self, backend, source_head='change', target_head='initial'):
343 commits = [
343 commits = [
344 {'message': 'initial'},
344 {'message': 'initial'},
345 {'message': 'change'},
345 {'message': 'change'},
346 {'message': 'new-feature', 'parents': ['initial']},
346 {'message': 'new-feature', 'parents': ['initial']},
347 ]
347 ]
348 self.commit_ids = backend.create_master_repo(commits)
348 self.commit_ids = backend.create_master_repo(commits)
349 self.source = backend.create_repo(heads=[source_head])
349 self.source = backend.create_repo(heads=[source_head])
350 self.target = backend.create_repo(heads=[target_head])
350 self.target = backend.create_repo(heads=[target_head])
351
351
352 data = {
352 data = {
353 'source_repo': self.source.repo_name,
353 'source_repo': self.source.repo_name,
354 'target_repo': self.target.repo_name,
354 'target_repo': self.target.repo_name,
355 'source_ref': self._get_full_ref(
355 'source_ref': self._get_full_ref(
356 backend, self.commit_ids[source_head]),
356 backend, self.commit_ids[source_head]),
357 'target_ref': self._get_full_ref(
357 'target_ref': self._get_full_ref(
358 backend, self.commit_ids[target_head]),
358 backend, self.commit_ids[target_head]),
359 'title': 'Test PR 1',
359 'title': 'Test PR 1',
360 'description': 'Test'
360 'description': 'Test'
361 }
361 }
362 RepoModel().grant_user_permission(
362 RepoModel().grant_user_permission(
363 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
363 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
364 return data
364 return data
365
365
366 def _get_full_ref(self, backend, commit_id):
366 def _get_full_ref(self, backend, commit_id):
367 return 'branch:{branch}:{commit_id}'.format(
367 return 'branch:{branch}:{commit_id}'.format(
368 branch=backend.default_branch_name, commit_id=commit_id)
368 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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,207 +1,207 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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 description='CTO of Things',
85 description='CTO of Things',
86 password='example')
86 password='example')
87 response = api_call(self.app, params)
87 response = api_call(self.app, params)
88
88
89 usr = UserModel().get_by_username(username)
89 usr = UserModel().get_by_username(username)
90 ret = {
90 ret = {
91 'msg': 'created new user `%s`' % (username,),
91 'msg': 'created new user `%s`' % (username,),
92 'user': jsonify(usr.get_api_data(include_secrets=True)),
92 'user': jsonify(usr.get_api_data(include_secrets=True)),
93 }
93 }
94 try:
94 try:
95 expected = ret
95 expected = ret
96 assert check_password('example', usr.password)
96 assert check_password('example', usr.password)
97 assert_ok(id_, expected, given=response.body)
97 assert_ok(id_, expected, given=response.body)
98 finally:
98 finally:
99 fixture.destroy_user(usr.user_id)
99 fixture.destroy_user(usr.user_id)
100
100
101 def test_api_create_user_without_password(self):
101 def test_api_create_user_without_password(self):
102 username = 'test_new_api_user_passwordless'
102 username = 'test_new_api_user_passwordless'
103 email = username + "@foo.com"
103 email = username + "@foo.com"
104
104
105 id_, params = build_data(
105 id_, params = build_data(
106 self.apikey, 'create_user',
106 self.apikey, 'create_user',
107 username=username,
107 username=username,
108 email=email)
108 email=email)
109 response = api_call(self.app, params)
109 response = api_call(self.app, params)
110
110
111 usr = UserModel().get_by_username(username)
111 usr = UserModel().get_by_username(username)
112 ret = {
112 ret = {
113 'msg': 'created new user `%s`' % (username,),
113 'msg': 'created new user `%s`' % (username,),
114 'user': jsonify(usr.get_api_data(include_secrets=True)),
114 'user': jsonify(usr.get_api_data(include_secrets=True)),
115 }
115 }
116 try:
116 try:
117 expected = ret
117 expected = ret
118 assert_ok(id_, expected, given=response.body)
118 assert_ok(id_, expected, given=response.body)
119 finally:
119 finally:
120 fixture.destroy_user(usr.user_id)
120 fixture.destroy_user(usr.user_id)
121
121
122 def test_api_create_user_with_extern_name(self):
122 def test_api_create_user_with_extern_name(self):
123 username = 'test_new_api_user_passwordless'
123 username = 'test_new_api_user_passwordless'
124 email = username + "@foo.com"
124 email = username + "@foo.com"
125
125
126 id_, params = build_data(
126 id_, params = build_data(
127 self.apikey, 'create_user',
127 self.apikey, 'create_user',
128 username=username,
128 username=username,
129 email=email, extern_name='rhodecode')
129 email=email, extern_name='rhodecode')
130 response = api_call(self.app, params)
130 response = api_call(self.app, params)
131
131
132 usr = UserModel().get_by_username(username)
132 usr = UserModel().get_by_username(username)
133 ret = {
133 ret = {
134 'msg': 'created new user `%s`' % (username,),
134 'msg': 'created new user `%s`' % (username,),
135 'user': jsonify(usr.get_api_data(include_secrets=True)),
135 'user': jsonify(usr.get_api_data(include_secrets=True)),
136 }
136 }
137 try:
137 try:
138 expected = ret
138 expected = ret
139 assert_ok(id_, expected, given=response.body)
139 assert_ok(id_, expected, given=response.body)
140 finally:
140 finally:
141 fixture.destroy_user(usr.user_id)
141 fixture.destroy_user(usr.user_id)
142
142
143 def test_api_create_user_with_password_change(self):
143 def test_api_create_user_with_password_change(self):
144 username = 'test_new_api_user_password_change'
144 username = 'test_new_api_user_password_change'
145 email = username + "@foo.com"
145 email = username + "@foo.com"
146
146
147 id_, params = build_data(
147 id_, params = build_data(
148 self.apikey, 'create_user',
148 self.apikey, 'create_user',
149 username=username,
149 username=username,
150 email=email, extern_name='rhodecode',
150 email=email, extern_name='rhodecode',
151 force_password_change=True)
151 force_password_change=True)
152 response = api_call(self.app, params)
152 response = api_call(self.app, params)
153
153
154 usr = UserModel().get_by_username(username)
154 usr = UserModel().get_by_username(username)
155 ret = {
155 ret = {
156 'msg': 'created new user `%s`' % (username,),
156 'msg': 'created new user `%s`' % (username,),
157 'user': jsonify(usr.get_api_data(include_secrets=True)),
157 'user': jsonify(usr.get_api_data(include_secrets=True)),
158 }
158 }
159 try:
159 try:
160 expected = ret
160 expected = ret
161 assert_ok(id_, expected, given=response.body)
161 assert_ok(id_, expected, given=response.body)
162 finally:
162 finally:
163 fixture.destroy_user(usr.user_id)
163 fixture.destroy_user(usr.user_id)
164
164
165 def test_api_create_user_with_personal_repo_group(self):
165 def test_api_create_user_with_personal_repo_group(self):
166 username = 'test_new_api_user_personal_group'
166 username = 'test_new_api_user_personal_group'
167 email = username + "@foo.com"
167 email = username + "@foo.com"
168
168
169 id_, params = build_data(
169 id_, params = build_data(
170 self.apikey, 'create_user',
170 self.apikey, 'create_user',
171 username=username,
171 username=username,
172 email=email, extern_name='rhodecode',
172 email=email, extern_name='rhodecode',
173 create_personal_repo_group=True)
173 create_personal_repo_group=True)
174 response = api_call(self.app, params)
174 response = api_call(self.app, params)
175
175
176 usr = UserModel().get_by_username(username)
176 usr = UserModel().get_by_username(username)
177 ret = {
177 ret = {
178 'msg': 'created new user `%s`' % (username,),
178 'msg': 'created new user `%s`' % (username,),
179 'user': jsonify(usr.get_api_data(include_secrets=True)),
179 'user': jsonify(usr.get_api_data(include_secrets=True)),
180 }
180 }
181
181
182 personal_group = RepoGroup.get_by_group_name(username)
182 personal_group = RepoGroup.get_by_group_name(username)
183 assert personal_group
183 assert personal_group
184 assert personal_group.personal == True
184 assert personal_group.personal == True
185 assert personal_group.user.username == username
185 assert personal_group.user.username == username
186
186
187 try:
187 try:
188 expected = ret
188 expected = ret
189 assert_ok(id_, expected, given=response.body)
189 assert_ok(id_, expected, given=response.body)
190 finally:
190 finally:
191 fixture.destroy_repo_group(username)
191 fixture.destroy_repo_group(username)
192 fixture.destroy_user(usr.user_id)
192 fixture.destroy_user(usr.user_id)
193
193
194 @mock.patch.object(UserModel, 'create_or_update', crash)
194 @mock.patch.object(UserModel, 'create_or_update', crash)
195 def test_api_create_user_when_exception_happened(self):
195 def test_api_create_user_when_exception_happened(self):
196
196
197 username = 'test_new_api_user'
197 username = 'test_new_api_user'
198 email = username + "@foo.com"
198 email = username + "@foo.com"
199
199
200 id_, params = build_data(
200 id_, params = build_data(
201 self.apikey, 'create_user',
201 self.apikey, 'create_user',
202 username=username,
202 username=username,
203 email=email,
203 email=email,
204 password='trololo')
204 password='trololo')
205 response = api_call(self.app, params)
205 response = api_call(self.app, params)
206 expected = 'failed to create user `%s`' % (username,)
206 expected = 'failed to create user `%s`' % (username,)
207 assert_error(id_, expected, given=response.body)
207 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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,115 +1,115 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22 from rhodecode.tests import HG_REPO
22 from rhodecode.tests import HG_REPO
23 from rhodecode.api.tests.utils import (
23 from rhodecode.api.tests.utils import (
24 build_data, api_call, assert_error, assert_ok)
24 build_data, api_call, assert_error, assert_ok)
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestApiSearch(object):
28 class TestApiSearch(object):
29
29
30 @pytest.mark.parametrize("sort_dir", [
30 @pytest.mark.parametrize("sort_dir", [
31 "asc",
31 "asc",
32 "desc",
32 "desc",
33 ])
33 ])
34 @pytest.mark.parametrize("sort", [
34 @pytest.mark.parametrize("sort", [
35 "xxx",
35 "xxx",
36 "author_email",
36 "author_email",
37 "date",
37 "date",
38 "message",
38 "message",
39 ])
39 ])
40 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
40 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
41 ('todo', 23, [
41 ('todo', 23, [
42 'vcs/backends/hg/inmemory.py',
42 'vcs/backends/hg/inmemory.py',
43 'vcs/tests/test_git.py']),
43 'vcs/tests/test_git.py']),
44 ('extension:rst installation', 6, [
44 ('extension:rst installation', 6, [
45 'docs/index.rst',
45 'docs/index.rst',
46 'docs/installation.rst']),
46 'docs/installation.rst']),
47 ('def repo', 87, [
47 ('def repo', 87, [
48 'vcs/tests/test_git.py',
48 'vcs/tests/test_git.py',
49 'vcs/tests/test_changesets.py']),
49 'vcs/tests/test_changesets.py']),
50 ('repository:%s def test' % HG_REPO, 18, [
50 ('repository:%s def test' % HG_REPO, 18, [
51 'vcs/tests/test_git.py',
51 'vcs/tests/test_git.py',
52 'vcs/tests/test_changesets.py']),
52 'vcs/tests/test_changesets.py']),
53 ('"def main"', 9, [
53 ('"def main"', 9, [
54 'vcs/__init__.py',
54 'vcs/__init__.py',
55 'vcs/tests/__init__.py',
55 'vcs/tests/__init__.py',
56 'vcs/utils/progressbar.py']),
56 'vcs/utils/progressbar.py']),
57 ('owner:test_admin', 358, [
57 ('owner:test_admin', 358, [
58 'vcs/tests/base.py',
58 'vcs/tests/base.py',
59 'MANIFEST.in',
59 'MANIFEST.in',
60 'vcs/utils/termcolors.py',
60 'vcs/utils/termcolors.py',
61 'docs/theme/ADC/static/documentation.png']),
61 'docs/theme/ADC/static/documentation.png']),
62 ('owner:test_admin def main', 72, [
62 ('owner:test_admin def main', 72, [
63 'vcs/__init__.py',
63 'vcs/__init__.py',
64 'vcs/tests/test_utils_filesize.py',
64 'vcs/tests/test_utils_filesize.py',
65 'vcs/tests/test_cli.py']),
65 'vcs/tests/test_cli.py']),
66 ('owner:michał test', 0, []),
66 ('owner:michał test', 0, []),
67 ])
67 ])
68 def test_search_content_results(self, sort_dir, sort, query, expected_hits, expected_paths):
68 def test_search_content_results(self, sort_dir, sort, query, expected_hits, expected_paths):
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey_regular, 'search',
70 self.apikey_regular, 'search',
71 search_query=query,
71 search_query=query,
72 search_sort='{}:{}'.format(sort_dir, sort),
72 search_sort='{}:{}'.format(sort_dir, sort),
73 search_type='content')
73 search_type='content')
74
74
75 response = api_call(self.app, params)
75 response = api_call(self.app, params)
76 json_response = response.json
76 json_response = response.json
77
77
78 assert json_response['result']['item_count'] == expected_hits
78 assert json_response['result']['item_count'] == expected_hits
79 paths = [x['f_path'] for x in json_response['result']['results']]
79 paths = [x['f_path'] for x in json_response['result']['results']]
80
80
81 for expected_path in expected_paths:
81 for expected_path in expected_paths:
82 assert expected_path in paths
82 assert expected_path in paths
83
83
84 @pytest.mark.parametrize("sort_dir", [
84 @pytest.mark.parametrize("sort_dir", [
85 "asc",
85 "asc",
86 "desc",
86 "desc",
87 ])
87 ])
88 @pytest.mark.parametrize("sort", [
88 @pytest.mark.parametrize("sort", [
89 "xxx",
89 "xxx",
90 "date",
90 "date",
91 "file",
91 "file",
92 "size",
92 "size",
93 ])
93 ])
94 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
94 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
95 ('readme.rst', 3, []),
95 ('readme.rst', 3, []),
96 ('test*', 75, []),
96 ('test*', 75, []),
97 ('*model*', 1, []),
97 ('*model*', 1, []),
98 ('extension:rst', 48, []),
98 ('extension:rst', 48, []),
99 ('extension:rst api', 24, []),
99 ('extension:rst api', 24, []),
100 ])
100 ])
101 def test_search_file_paths(self, sort_dir, sort, query, expected_hits, expected_paths):
101 def test_search_file_paths(self, sort_dir, sort, query, expected_hits, expected_paths):
102 id_, params = build_data(
102 id_, params = build_data(
103 self.apikey_regular, 'search',
103 self.apikey_regular, 'search',
104 search_query=query,
104 search_query=query,
105 search_sort='{}:{}'.format(sort_dir, sort),
105 search_sort='{}:{}'.format(sort_dir, sort),
106 search_type='path')
106 search_type='path')
107
107
108 response = api_call(self.app, params)
108 response = api_call(self.app, params)
109 json_response = response.json
109 json_response = response.json
110
110
111 assert json_response['result']['item_count'] == expected_hits
111 assert json_response['result']['item_count'] == expected_hits
112 paths = [x['f_path'] for x in json_response['result']['results']]
112 paths = [x['f_path'] for x in json_response['result']['results']]
113
113
114 for expected_path in expected_paths:
114 for expected_path in expected_paths:
115 assert expected_path in paths
115 assert expected_path in paths
@@ -1,101 +1,101 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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,61 +1,61 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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', 'get_repo_comments']
41 'get_pull_request_comments', 'comment_commit', 'get_repo_comments']
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 'extra_recipients': '<Optional:[]>',
53 'extra_recipients': '<Optional:[]>',
54 'message': '<RequiredType>',
54 'message': '<RequiredType>',
55 'repoid': '<RequiredType>',
55 'repoid': '<RequiredType>',
56 'request': '<RequiredType>',
56 'request': '<RequiredType>',
57 'resolves_comment_id': '<Optional:None>',
57 'resolves_comment_id': '<Optional:None>',
58 'status': '<Optional:None>',
58 'status': '<Optional:None>',
59 'userid': '<Optional:<OptionalAttr:apiuser>>',
59 'userid': '<Optional:<OptionalAttr:apiuser>>',
60 'send_email': '<Optional:True>'}]
60 'send_email': '<Optional:True>'}]
61 assert_ok(id_, expected, given=response.body)
61 assert_ok(id_, expected, given=response.body)
@@ -1,143 +1,143 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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, merge_state=True)
41 pullrequestid=pull_request.pull_request_id, merge_state=True)
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 'state': pull_request.pull_request_state,
68 'state': pull_request.pull_request_state,
69 'created_on': pull_request.created_on,
69 'created_on': pull_request.created_on,
70 'updated_on': pull_request.updated_on,
70 'updated_on': pull_request.updated_on,
71 'commit_ids': pull_request.revisions,
71 'commit_ids': pull_request.revisions,
72 'review_status': pull_request.calculated_review_status(),
72 'review_status': pull_request.calculated_review_status(),
73 'mergeable': {
73 'mergeable': {
74 'status': True,
74 'status': True,
75 'message': 'This pull request can be automatically merged.',
75 'message': 'This pull request can be automatically merged.',
76 },
76 },
77 'source': {
77 'source': {
78 'clone_url': source_url,
78 'clone_url': source_url,
79 'repository': pull_request.source_repo.repo_name,
79 'repository': pull_request.source_repo.repo_name,
80 'reference': {
80 'reference': {
81 'name': pull_request.source_ref_parts.name,
81 'name': pull_request.source_ref_parts.name,
82 'type': pull_request.source_ref_parts.type,
82 'type': pull_request.source_ref_parts.type,
83 'commit_id': pull_request.source_ref_parts.commit_id,
83 'commit_id': pull_request.source_ref_parts.commit_id,
84 },
84 },
85 },
85 },
86 'target': {
86 'target': {
87 'clone_url': target_url,
87 'clone_url': target_url,
88 'repository': pull_request.target_repo.repo_name,
88 'repository': pull_request.target_repo.repo_name,
89 'reference': {
89 'reference': {
90 'name': pull_request.target_ref_parts.name,
90 'name': pull_request.target_ref_parts.name,
91 'type': pull_request.target_ref_parts.type,
91 'type': pull_request.target_ref_parts.type,
92 'commit_id': pull_request.target_ref_parts.commit_id,
92 'commit_id': pull_request.target_ref_parts.commit_id,
93 },
93 },
94 },
94 },
95 'merge': {
95 'merge': {
96 'clone_url': shadow_url,
96 'clone_url': shadow_url,
97 'reference': {
97 'reference': {
98 'name': pull_request.shadow_merge_ref.name,
98 'name': pull_request.shadow_merge_ref.name,
99 'type': pull_request.shadow_merge_ref.type,
99 'type': pull_request.shadow_merge_ref.type,
100 'commit_id': pull_request.shadow_merge_ref.commit_id,
100 'commit_id': pull_request.shadow_merge_ref.commit_id,
101 },
101 },
102 },
102 },
103 'author': pull_request.author.get_api_data(include_secrets=False,
103 'author': pull_request.author.get_api_data(include_secrets=False,
104 details='basic'),
104 details='basic'),
105 'reviewers': [
105 'reviewers': [
106 {
106 {
107 'user': reviewer.get_api_data(include_secrets=False,
107 'user': reviewer.get_api_data(include_secrets=False,
108 details='basic'),
108 details='basic'),
109 'reasons': reasons,
109 'reasons': reasons,
110 'review_status': st[0][1].status if st else 'not_reviewed',
110 'review_status': st[0][1].status if st else 'not_reviewed',
111 }
111 }
112 for obj, reviewer, reasons, mandatory, st in
112 for obj, reviewer, reasons, mandatory, st in
113 pull_request.reviewers_statuses()
113 pull_request.reviewers_statuses()
114 ]
114 ]
115 }
115 }
116 assert_ok(id_, expected, response.body)
116 assert_ok(id_, expected, response.body)
117
117
118 def test_api_get_pull_request_repo_error(self, pr_util):
118 def test_api_get_pull_request_repo_error(self, pr_util):
119 pull_request = pr_util.create_pull_request()
119 pull_request = pr_util.create_pull_request()
120 id_, params = build_data(
120 id_, params = build_data(
121 self.apikey, 'get_pull_request',
121 self.apikey, 'get_pull_request',
122 repoid=666, pullrequestid=pull_request.pull_request_id)
122 repoid=666, pullrequestid=pull_request.pull_request_id)
123 response = api_call(self.app, params)
123 response = api_call(self.app, params)
124
124
125 expected = 'repository `666` does not exist'
125 expected = 'repository `666` does not exist'
126 assert_error(id_, expected, given=response.body)
126 assert_error(id_, expected, given=response.body)
127
127
128 def test_api_get_pull_request_pull_request_error(self):
128 def test_api_get_pull_request_pull_request_error(self):
129 id_, params = build_data(
129 id_, params = build_data(
130 self.apikey, 'get_pull_request', pullrequestid=666)
130 self.apikey, 'get_pull_request', pullrequestid=666)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132
132
133 expected = 'pull request `666` does not exist'
133 expected = 'pull request `666` does not exist'
134 assert_error(id_, expected, given=response.body)
134 assert_error(id_, expected, given=response.body)
135
135
136 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
136 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
137 id_, params = build_data(
137 id_, params = build_data(
138 self.apikey, 'get_pull_request',
138 self.apikey, 'get_pull_request',
139 pullrequestid=666)
139 pullrequestid=666)
140 response = api_call(self.app, params)
140 response = api_call(self.app, params)
141
141
142 expected = 'pull request `666` does not exist'
142 expected = 'pull request `666` does not exist'
143 assert_error(id_, expected, given=response.body)
143 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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 'comment_resolved_by': None,
62 'comment_resolved_by': None,
63 'pull_request_version': None,
63 'pull_request_version': None,
64 'comment_commit_id': None,
64 'comment_commit_id': None,
65 'comment_pull_request_id': pull_request.pull_request_id
65 'comment_pull_request_id': pull_request.pull_request_id
66 }
66 }
67 ]
67 ]
68 assert_ok(id_, expected, response.body)
68 assert_ok(id_, expected, response.body)
69
69
70 def test_api_get_pull_request_comments_repo_error(self, pr_util):
70 def test_api_get_pull_request_comments_repo_error(self, pr_util):
71 pull_request = pr_util.create_pull_request()
71 pull_request = pr_util.create_pull_request()
72 id_, params = build_data(
72 id_, params = build_data(
73 self.apikey, 'get_pull_request_comments',
73 self.apikey, 'get_pull_request_comments',
74 repoid=666, pullrequestid=pull_request.pull_request_id)
74 repoid=666, pullrequestid=pull_request.pull_request_id)
75 response = api_call(self.app, params)
75 response = api_call(self.app, params)
76
76
77 expected = 'repository `666` does not exist'
77 expected = 'repository `666` does not exist'
78 assert_error(id_, expected, given=response.body)
78 assert_error(id_, expected, given=response.body)
79
79
80 def test_api_get_pull_request_comments_pull_request_error(self):
80 def test_api_get_pull_request_comments_pull_request_error(self):
81 id_, params = build_data(
81 id_, params = build_data(
82 self.apikey, 'get_pull_request_comments', pullrequestid=666)
82 self.apikey, 'get_pull_request_comments', pullrequestid=666)
83 response = api_call(self.app, params)
83 response = api_call(self.app, params)
84
84
85 expected = 'pull request `666` does not exist'
85 expected = 'pull request `666` does not exist'
86 assert_error(id_, expected, given=response.body)
86 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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,143 +1,143 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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 repo_id = repo.repo_id
47 repo_id = repo.repo_id
48 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
48 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
49 group = user_util.create_user_group(members=[usr])
49 group = user_util.create_user_group(members=[usr])
50 user_util.grant_user_group_permission_to_repo(
50 user_util.grant_user_group_permission_to_repo(
51 repo=repo, user_group=group, permission_name='repository.read')
51 repo=repo, user_group=group, permission_name='repository.read')
52 Session().commit()
52 Session().commit()
53 kwargs = {
53 kwargs = {
54 'repoid': repo.repo_name,
54 'repoid': repo.repo_name,
55 }
55 }
56 if cache_param is not None:
56 if cache_param is not None:
57 kwargs['cache'] = cache_param
57 kwargs['cache'] = cache_param
58
58
59 apikey = getattr(self, apikey_attr)
59 apikey = getattr(self, apikey_attr)
60 id_, params = build_data(apikey, 'get_repo', **kwargs)
60 id_, params = build_data(apikey, 'get_repo', **kwargs)
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62
62
63 ret = repo.get_api_data()
63 ret = repo.get_api_data()
64
64
65 permissions = expected_permissions(repo)
65 permissions = expected_permissions(repo)
66
66
67 followers = []
67 followers = []
68
68
69 repo = RepoModel().get(repo_id)
69 repo = RepoModel().get(repo_id)
70 for user in repo.followers:
70 for user in repo.followers:
71 followers.append(user.user.get_api_data(
71 followers.append(user.user.get_api_data(
72 include_secrets=expect_secrets))
72 include_secrets=expect_secrets))
73
73
74 ret['permissions'] = permissions
74 ret['permissions'] = permissions
75 ret['followers'] = followers
75 ret['followers'] = followers
76
76
77 expected = ret
77 expected = ret
78
78
79 assert_ok(id_, expected, given=response.body)
79 assert_ok(id_, expected, given=response.body)
80
80
81 @pytest.mark.parametrize("grant_perm", [
81 @pytest.mark.parametrize("grant_perm", [
82 'repository.admin',
82 'repository.admin',
83 'repository.write',
83 'repository.write',
84 'repository.read',
84 'repository.read',
85 ])
85 ])
86 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
86 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
87 # TODO: Depending on which tests are running before this one, we
87 # TODO: Depending on which tests are running before this one, we
88 # start with a different number of permissions in the database.
88 # start with a different number of permissions in the database.
89 repo = RepoModel().get_by_repo_name(backend.repo_name)
89 repo = RepoModel().get_by_repo_name(backend.repo_name)
90 repo_id = repo.repo_id
90 repo_id = repo.repo_id
91 permission_count = len(repo.repo_to_perm)
91 permission_count = len(repo.repo_to_perm)
92
92
93 RepoModel().grant_user_permission(repo=backend.repo_name,
93 RepoModel().grant_user_permission(repo=backend.repo_name,
94 user=self.TEST_USER_LOGIN,
94 user=self.TEST_USER_LOGIN,
95 perm=grant_perm)
95 perm=grant_perm)
96 Session().commit()
96 Session().commit()
97 id_, params = build_data(
97 id_, params = build_data(
98 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
98 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
99 response = api_call(self.app, params)
99 response = api_call(self.app, params)
100
100
101 repo = RepoModel().get_by_repo_name(backend.repo_name)
101 repo = RepoModel().get_by_repo_name(backend.repo_name)
102 ret = repo.get_api_data()
102 ret = repo.get_api_data()
103
103
104 assert permission_count + 1, len(repo.repo_to_perm)
104 assert permission_count + 1, len(repo.repo_to_perm)
105
105
106 permissions = expected_permissions(repo)
106 permissions = expected_permissions(repo)
107
107
108 followers = []
108 followers = []
109
109
110 repo = RepoModel().get(repo_id)
110 repo = RepoModel().get(repo_id)
111 for user in repo.followers:
111 for user in repo.followers:
112 followers.append(user.user.get_api_data())
112 followers.append(user.user.get_api_data())
113
113
114 ret['permissions'] = permissions
114 ret['permissions'] = permissions
115 ret['followers'] = followers
115 ret['followers'] = followers
116
116
117 expected = ret
117 expected = ret
118 try:
118 try:
119 assert_ok(id_, expected, given=response.body)
119 assert_ok(id_, expected, given=response.body)
120 finally:
120 finally:
121 RepoModel().revoke_user_permission(
121 RepoModel().revoke_user_permission(
122 backend.repo_name, self.TEST_USER_LOGIN)
122 backend.repo_name, self.TEST_USER_LOGIN)
123
123
124 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
124 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
125 RepoModel().grant_user_permission(repo=backend.repo_name,
125 RepoModel().grant_user_permission(repo=backend.repo_name,
126 user=self.TEST_USER_LOGIN,
126 user=self.TEST_USER_LOGIN,
127 perm='repository.none')
127 perm='repository.none')
128
128
129 id_, params = build_data(
129 id_, params = build_data(
130 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
130 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132
132
133 expected = 'repository `%s` does not exist' % (backend.repo_name)
133 expected = 'repository `%s` does not exist' % (backend.repo_name)
134 assert_error(id_, expected, given=response.body)
134 assert_error(id_, expected, given=response.body)
135
135
136 def test_api_get_repo_not_existing(self):
136 def test_api_get_repo_not_existing(self):
137 id_, params = build_data(
137 id_, params = build_data(
138 self.apikey, 'get_repo', repoid='no-such-repo')
138 self.apikey, 'get_repo', repoid='no-such-repo')
139 response = api_call(self.app, params)
139 response = api_call(self.app, params)
140
140
141 ret = 'repository `%s` does not exist' % 'no-such-repo'
141 ret = 'repository `%s` does not exist' % 'no-such-repo'
142 expected = ret
142 expected = ret
143 assert_error(id_, expected, given=response.body)
143 assert_error(id_, expected, given=response.body)
@@ -1,141 +1,141 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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 branches = [commit.branch] if commit.branch else []
45 branches = [commit.branch] if commit.branch else []
46 assert result['refs']['branches'] == branches
46 assert result['refs']['branches'] == branches
47 assert result['refs']['tags'] == commit.tags
47 assert result['refs']['tags'] == commit.tags
48
48
49 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
49 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
50 def test_get_repo_changeset_bad_type(self, details, backend):
50 def test_get_repo_changeset_bad_type(self, details, backend):
51 id_, params = build_data(
51 id_, params = build_data(
52 self.apikey, 'get_repo_changeset',
52 self.apikey, 'get_repo_changeset',
53 repoid=backend.repo_name, revision=0,
53 repoid=backend.repo_name, revision=0,
54 details=details,
54 details=details,
55 )
55 )
56 response = api_call(self.app, params)
56 response = api_call(self.app, params)
57 expected = "commit_id must be a string value got <type 'int'> instead"
57 expected = "commit_id must be a string value got <type 'int'> instead"
58 assert_error(id_, expected, given=response.body)
58 assert_error(id_, expected, given=response.body)
59
59
60 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
60 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
61 def test_get_repo_changesets(self, details, backend):
61 def test_get_repo_changesets(self, details, backend):
62 limit = 2
62 limit = 2
63 commit = backend.repo.get_commit(commit_idx=0)
63 commit = backend.repo.get_commit(commit_idx=0)
64 __, params = build_data(
64 __, params = build_data(
65 self.apikey, 'get_repo_changesets',
65 self.apikey, 'get_repo_changesets',
66 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
66 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
67 details=details,
67 details=details,
68 )
68 )
69 response = api_call(self.app, params)
69 response = api_call(self.app, params)
70 result = response.json['result']
70 result = response.json['result']
71 assert result
71 assert result
72 assert len(result) == limit
72 assert len(result) == limit
73 for x in xrange(limit):
73 for x in xrange(limit):
74 assert result[x]['revision'] == x
74 assert result[x]['revision'] == x
75
75
76 if details == 'full':
76 if details == 'full':
77 for x in xrange(limit):
77 for x in xrange(limit):
78 assert 'bookmarks' in result[x]['refs']
78 assert 'bookmarks' in result[x]['refs']
79 assert 'branches' in result[x]['refs']
79 assert 'branches' in result[x]['refs']
80 assert 'tags' in result[x]['refs']
80 assert 'tags' in result[x]['refs']
81
81
82 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
82 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
83 @pytest.mark.parametrize("start_rev, expected_revision", [
83 @pytest.mark.parametrize("start_rev, expected_revision", [
84 ("0", 0),
84 ("0", 0),
85 ("10", 10),
85 ("10", 10),
86 ("20", 20),
86 ("20", 20),
87 ])
87 ])
88 @pytest.mark.backends("hg", "git")
88 @pytest.mark.backends("hg", "git")
89 def test_get_repo_changesets_commit_range(
89 def test_get_repo_changesets_commit_range(
90 self, details, backend, start_rev, expected_revision):
90 self, details, backend, start_rev, expected_revision):
91 limit = 10
91 limit = 10
92 __, params = build_data(
92 __, params = build_data(
93 self.apikey, 'get_repo_changesets',
93 self.apikey, 'get_repo_changesets',
94 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
94 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
95 details=details,
95 details=details,
96 )
96 )
97 response = api_call(self.app, params)
97 response = api_call(self.app, params)
98 result = response.json['result']
98 result = response.json['result']
99 assert result
99 assert result
100 assert len(result) == limit
100 assert len(result) == limit
101 for i in xrange(limit):
101 for i in xrange(limit):
102 assert result[i]['revision'] == int(expected_revision) + i
102 assert result[i]['revision'] == int(expected_revision) + i
103
103
104 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
104 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
105 @pytest.mark.parametrize("start_rev, expected_revision", [
105 @pytest.mark.parametrize("start_rev, expected_revision", [
106 ("0", 0),
106 ("0", 0),
107 ("10", 9),
107 ("10", 9),
108 ("20", 19),
108 ("20", 19),
109 ])
109 ])
110 def test_get_repo_changesets_commit_range_svn(
110 def test_get_repo_changesets_commit_range_svn(
111 self, details, backend_svn, start_rev, expected_revision):
111 self, details, backend_svn, start_rev, expected_revision):
112
112
113 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
113 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
114 # in our API allows to pass in a "Commit ID" as well as a
114 # in our API allows to pass in a "Commit ID" as well as a
115 # "Commit Index". In the case of Subversion it is not possible to
115 # "Commit Index". In the case of Subversion it is not possible to
116 # distinguish these cases. As a workaround we implemented this
116 # distinguish these cases. As a workaround we implemented this
117 # behavior which gives a preference to see it as a "Commit ID".
117 # behavior which gives a preference to see it as a "Commit ID".
118
118
119 limit = 10
119 limit = 10
120 __, params = build_data(
120 __, params = build_data(
121 self.apikey, 'get_repo_changesets',
121 self.apikey, 'get_repo_changesets',
122 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
122 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
123 details=details,
123 details=details,
124 )
124 )
125 response = api_call(self.app, params)
125 response = api_call(self.app, params)
126 result = response.json['result']
126 result = response.json['result']
127 assert result
127 assert result
128 assert len(result) == limit
128 assert len(result) == limit
129 for i in xrange(limit):
129 for i in xrange(limit):
130 assert result[i]['revision'] == int(expected_revision) + i
130 assert result[i]['revision'] == int(expected_revision) + i
131
131
132 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
132 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
133 def test_get_repo_changesets_bad_type(self, details, backend):
133 def test_get_repo_changesets_bad_type(self, details, backend):
134 id_, params = build_data(
134 id_, params = build_data(
135 self.apikey, 'get_repo_changesets',
135 self.apikey, 'get_repo_changesets',
136 repoid=backend.repo_name, start_rev=0, limit=2,
136 repoid=backend.repo_name, start_rev=0, limit=2,
137 details=details,
137 details=details,
138 )
138 )
139 response = api_call(self.app, params)
139 response = api_call(self.app, params)
140 expected = "commit_id must be a string value got <type 'int'> instead"
140 expected = "commit_id must be a string value got <type 'int'> instead"
141 assert_error(id_, expected, given=response.body)
141 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import User, ChangesetComment
24 from rhodecode.model.db import User, ChangesetComment
25 from rhodecode.model.meta import Session
25 from rhodecode.model.meta import Session
26 from rhodecode.model.comment import CommentsModel
26 from rhodecode.model.comment import CommentsModel
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_error, assert_call_ok)
28 build_data, api_call, assert_error, assert_call_ok)
29
29
30
30
31 @pytest.fixture()
31 @pytest.fixture()
32 def make_repo_comments_factory(request):
32 def make_repo_comments_factory(request):
33
33
34 class Make(object):
34 class Make(object):
35
35
36 def make_comments(self, repo):
36 def make_comments(self, repo):
37 user = User.get_first_super_admin()
37 user = User.get_first_super_admin()
38 commit = repo.scm_instance()[0]
38 commit = repo.scm_instance()[0]
39
39
40 commit_id = commit.raw_id
40 commit_id = commit.raw_id
41 file_0 = commit.affected_files[0]
41 file_0 = commit.affected_files[0]
42 comments = []
42 comments = []
43
43
44 # general
44 # general
45 CommentsModel().create(
45 CommentsModel().create(
46 text='General Comment', repo=repo, user=user, commit_id=commit_id,
46 text='General Comment', repo=repo, user=user, commit_id=commit_id,
47 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
47 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
48
48
49 # inline
49 # inline
50 CommentsModel().create(
50 CommentsModel().create(
51 text='Inline Comment', repo=repo, user=user, commit_id=commit_id,
51 text='Inline Comment', repo=repo, user=user, commit_id=commit_id,
52 f_path=file_0, line_no='n1',
52 f_path=file_0, line_no='n1',
53 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
53 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
54
54
55 # todo
55 # todo
56 CommentsModel().create(
56 CommentsModel().create(
57 text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id,
57 text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id,
58 f_path=file_0, line_no='n1',
58 f_path=file_0, line_no='n1',
59 comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False)
59 comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False)
60
60
61 @request.addfinalizer
61 @request.addfinalizer
62 def cleanup():
62 def cleanup():
63 for comment in comments:
63 for comment in comments:
64 Session().delete(comment)
64 Session().delete(comment)
65 return Make()
65 return Make()
66
66
67
67
68 @pytest.mark.usefixtures("testuser_api", "app")
68 @pytest.mark.usefixtures("testuser_api", "app")
69 class TestGetRepo(object):
69 class TestGetRepo(object):
70
70
71 @pytest.mark.parametrize('filters, expected_count', [
71 @pytest.mark.parametrize('filters, expected_count', [
72 ({}, 3),
72 ({}, 3),
73 ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2),
73 ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2),
74 ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1),
74 ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1),
75 ({'commit_id': 'FILLED DYNAMIC'}, 3),
75 ({'commit_id': 'FILLED DYNAMIC'}, 3),
76 ])
76 ])
77 def test_api_get_repo_comments(self, backend, user_util,
77 def test_api_get_repo_comments(self, backend, user_util,
78 make_repo_comments_factory, filters, expected_count):
78 make_repo_comments_factory, filters, expected_count):
79 commits = [{'message': 'A'}, {'message': 'B'}]
79 commits = [{'message': 'A'}, {'message': 'B'}]
80 repo = backend.create_repo(commits=commits)
80 repo = backend.create_repo(commits=commits)
81 make_repo_comments_factory.make_comments(repo)
81 make_repo_comments_factory.make_comments(repo)
82
82
83 api_call_params = {'repoid': repo.repo_name,}
83 api_call_params = {'repoid': repo.repo_name,}
84 api_call_params.update(filters)
84 api_call_params.update(filters)
85
85
86 if 'commit_id' in api_call_params:
86 if 'commit_id' in api_call_params:
87 commit = repo.scm_instance()[0]
87 commit = repo.scm_instance()[0]
88 commit_id = commit.raw_id
88 commit_id = commit.raw_id
89 api_call_params['commit_id'] = commit_id
89 api_call_params['commit_id'] = commit_id
90
90
91 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
91 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
92 response = api_call(self.app, params)
92 response = api_call(self.app, params)
93 result = assert_call_ok(id_, given=response.body)
93 result = assert_call_ok(id_, given=response.body)
94
94
95 assert len(result) == expected_count
95 assert len(result) == expected_count
96
96
97 def test_api_get_repo_comments_wrong_comment_type(
97 def test_api_get_repo_comments_wrong_comment_type(
98 self, make_repo_comments_factory, backend_hg):
98 self, make_repo_comments_factory, backend_hg):
99 commits = [{'message': 'A'}, {'message': 'B'}]
99 commits = [{'message': 'A'}, {'message': 'B'}]
100 repo = backend_hg.create_repo(commits=commits)
100 repo = backend_hg.create_repo(commits=commits)
101 make_repo_comments_factory.make_comments(repo)
101 make_repo_comments_factory.make_comments(repo)
102
102
103 api_call_params = {'repoid': repo.repo_name}
103 api_call_params = {'repoid': repo.repo_name}
104 api_call_params.update({'comment_type': 'bogus'})
104 api_call_params.update({'comment_type': 'bogus'})
105
105
106 expected = 'comment_type must be one of `{}` got {}'.format(
106 expected = 'comment_type must be one of `{}` got {}'.format(
107 ChangesetComment.COMMENT_TYPES, 'bogus')
107 ChangesetComment.COMMENT_TYPES, 'bogus')
108 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
108 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
109 response = api_call(self.app, params)
109 response = api_call(self.app, params)
110 assert_error(id_, expected, given=response.body)
110 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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,173 +1,173 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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 user_util.create_repo(parent=repo_group)
57 user_util.create_repo(parent=repo_group)
58
58
59 id_, params = build_data(
59 id_, params = build_data(
60 self.apikey,
60 self.apikey,
61 'grant_user_group_permission_to_repo_group',
61 'grant_user_group_permission_to_repo_group',
62 repogroupid=repo_group.name,
62 repogroupid=repo_group.name,
63 usergroupid=user_group.users_group_name,
63 usergroupid=user_group.users_group_name,
64 perm=perm,
64 perm=perm,
65 apply_to_children=apply_to_children,)
65 apply_to_children=apply_to_children,)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 ret = {
68 ret = {
69 'msg': (
69 'msg': (
70 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
70 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
71 ' in repo group: `%s`' % (
71 ' in repo group: `%s`' % (
72 perm, apply_to_children, user_group.users_group_name,
72 perm, apply_to_children, user_group.users_group_name,
73 repo_group.name
73 repo_group.name
74 )
74 )
75 ),
75 ),
76 'success': True
76 'success': True
77 }
77 }
78 expected = ret
78 expected = ret
79 try:
79 try:
80 assert_ok(id_, expected, given=response.body)
80 assert_ok(id_, expected, given=response.body)
81 finally:
81 finally:
82 RepoGroupModel().revoke_user_group_permission(
82 RepoGroupModel().revoke_user_group_permission(
83 repo_group.group_id, user_group.users_group_id)
83 repo_group.group_id, user_group.users_group_id)
84
84
85 @pytest.mark.parametrize(
85 @pytest.mark.parametrize(
86 "name, perm, apply_to_children, grant_admin, access_ok", [
86 "name, perm, apply_to_children, grant_admin, access_ok", [
87 ('none_fails', 'group.none', 'none', False, False),
87 ('none_fails', 'group.none', 'none', False, False),
88 ('read_fails', 'group.read', 'none', False, False),
88 ('read_fails', 'group.read', 'none', False, False),
89 ('write_fails', 'group.write', 'none', False, False),
89 ('write_fails', 'group.write', 'none', False, False),
90 ('admin_fails', 'group.admin', 'none', False, False),
90 ('admin_fails', 'group.admin', 'none', False, False),
91
91
92 # with granted perms
92 # with granted perms
93 ('none_ok', 'group.none', 'none', True, True),
93 ('none_ok', 'group.none', 'none', True, True),
94 ('read_ok', 'group.read', 'none', True, True),
94 ('read_ok', 'group.read', 'none', True, True),
95 ('write_ok', 'group.write', 'none', True, True),
95 ('write_ok', 'group.write', 'none', True, True),
96 ('admin_ok', 'group.admin', 'none', True, True),
96 ('admin_ok', 'group.admin', 'none', True, True),
97 ]
97 ]
98 )
98 )
99 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
99 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
100 self, name, perm, apply_to_children, grant_admin, access_ok,
100 self, name, perm, apply_to_children, grant_admin, access_ok,
101 user_util):
101 user_util):
102 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
102 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
103 user_group = user_util.create_user_group()
103 user_group = user_util.create_user_group()
104 repo_group = user_util.create_repo_group()
104 repo_group = user_util.create_repo_group()
105 if grant_admin:
105 if grant_admin:
106 user_util.grant_user_permission_to_repo_group(
106 user_util.grant_user_permission_to_repo_group(
107 repo_group, user, 'group.admin')
107 repo_group, user, 'group.admin')
108
108
109 id_, params = build_data(
109 id_, params = build_data(
110 self.apikey_regular,
110 self.apikey_regular,
111 'grant_user_group_permission_to_repo_group',
111 'grant_user_group_permission_to_repo_group',
112 repogroupid=repo_group.name,
112 repogroupid=repo_group.name,
113 usergroupid=user_group.users_group_name,
113 usergroupid=user_group.users_group_name,
114 perm=perm,
114 perm=perm,
115 apply_to_children=apply_to_children,)
115 apply_to_children=apply_to_children,)
116 response = api_call(self.app, params)
116 response = api_call(self.app, params)
117 if access_ok:
117 if access_ok:
118 ret = {
118 ret = {
119 'msg': (
119 'msg': (
120 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
120 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
121 ' in repo group: `%s`' % (
121 ' in repo group: `%s`' % (
122 perm, apply_to_children, user_group.users_group_name,
122 perm, apply_to_children, user_group.users_group_name,
123 repo_group.name
123 repo_group.name
124 )
124 )
125 ),
125 ),
126 'success': True
126 'success': True
127 }
127 }
128 expected = ret
128 expected = ret
129 try:
129 try:
130 assert_ok(id_, expected, given=response.body)
130 assert_ok(id_, expected, given=response.body)
131 finally:
131 finally:
132 RepoGroupModel().revoke_user_group_permission(
132 RepoGroupModel().revoke_user_group_permission(
133 repo_group.group_id, user_group.users_group_id)
133 repo_group.group_id, user_group.users_group_id)
134 else:
134 else:
135 expected = 'repository group `%s` does not exist' % (repo_group.name,)
135 expected = 'repository group `%s` does not exist' % (repo_group.name,)
136 assert_error(id_, expected, given=response.body)
136 assert_error(id_, expected, given=response.body)
137
137
138 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
138 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
139 self, user_util):
139 self, user_util):
140 user_group = user_util.create_user_group()
140 user_group = user_util.create_user_group()
141 repo_group = user_util.create_repo_group()
141 repo_group = user_util.create_repo_group()
142 perm = 'haha.no.permission'
142 perm = 'haha.no.permission'
143 id_, params = build_data(
143 id_, params = build_data(
144 self.apikey,
144 self.apikey,
145 'grant_user_group_permission_to_repo_group',
145 'grant_user_group_permission_to_repo_group',
146 repogroupid=repo_group.name,
146 repogroupid=repo_group.name,
147 usergroupid=user_group.users_group_name,
147 usergroupid=user_group.users_group_name,
148 perm=perm)
148 perm=perm)
149 response = api_call(self.app, params)
149 response = api_call(self.app, params)
150
150
151 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,)
151 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,)
152 assert_error(id_, expected, given=response.body)
152 assert_error(id_, expected, given=response.body)
153
153
154 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
154 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
155 def test_api_grant_user_group_permission_exception_when_adding_2(
155 def test_api_grant_user_group_permission_exception_when_adding_2(
156 self, user_util):
156 self, user_util):
157 user_group = user_util.create_user_group()
157 user_group = user_util.create_user_group()
158 repo_group = user_util.create_repo_group()
158 repo_group = user_util.create_repo_group()
159 perm = 'group.read'
159 perm = 'group.read'
160 id_, params = build_data(
160 id_, params = build_data(
161 self.apikey,
161 self.apikey,
162 'grant_user_group_permission_to_repo_group',
162 'grant_user_group_permission_to_repo_group',
163 repogroupid=repo_group.name,
163 repogroupid=repo_group.name,
164 usergroupid=user_group.users_group_name,
164 usergroupid=user_group.users_group_name,
165 perm=perm)
165 perm=perm)
166 response = api_call(self.app, params)
166 response = api_call(self.app, params)
167
167
168 expected = (
168 expected = (
169 'failed to edit permission for user group: `%s`'
169 'failed to edit permission for user group: `%s`'
170 ' in repo group: `%s`' % (
170 ' in repo group: `%s`' % (
171 user_group.users_group_name, repo_group.name)
171 user_group.users_group_name, repo_group.name)
172 )
172 )
173 assert_error(id_, expected, given=response.body)
173 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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. Permission should start with prefix: `group.`' % (perm,)
135 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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. Permission should start with prefix: `usergroup.`' % perm
133 expected = 'permission `%s` does not exist. Permission should start with prefix: `usergroup.`' % 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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,259 +1,259 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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
32
33 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
34 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
35 pull_request = pr_util.create_pull_request(mergeable=True)
35 pull_request = pr_util.create_pull_request(mergeable=True)
36 pull_request_id = pull_request.pull_request_id
36 pull_request_id = pull_request.pull_request_id
37 pull_request_repo = pull_request.target_repo.repo_name
37 pull_request_repo = pull_request.target_repo.repo_name
38
38
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'merge_pull_request',
40 self.apikey, 'merge_pull_request',
41 repoid=pull_request_repo,
41 repoid=pull_request_repo,
42 pullrequestid=pull_request_id)
42 pullrequestid=pull_request_id)
43
43
44 response = api_call(self.app, params)
44 response = api_call(self.app, params)
45
45
46 # The above api call detaches the pull request DB object from the
46 # The above api call detaches the pull request DB object from the
47 # session because of an unconditional transaction rollback in our
47 # session because of an unconditional transaction rollback in our
48 # middleware. Therefore we need to add it back here if we want to use it.
48 # middleware. Therefore we need to add it back here if we want to use it.
49 Session().add(pull_request)
49 Session().add(pull_request)
50
50
51 expected = 'merge not possible for following reasons: ' \
51 expected = 'merge not possible for following reasons: ' \
52 'Pull request reviewer approval is pending.'
52 'Pull request reviewer approval is pending.'
53 assert_error(id_, expected, given=response.body)
53 assert_error(id_, expected, given=response.body)
54
54
55 @pytest.mark.backends("git", "hg")
55 @pytest.mark.backends("git", "hg")
56 def test_api_merge_pull_request_merge_failed_disallowed_state(
56 def test_api_merge_pull_request_merge_failed_disallowed_state(
57 self, pr_util, no_notifications):
57 self, pr_util, no_notifications):
58 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
58 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
59 pull_request_id = pull_request.pull_request_id
59 pull_request_id = pull_request.pull_request_id
60 pull_request_repo = pull_request.target_repo.repo_name
60 pull_request_repo = pull_request.target_repo.repo_name
61
61
62 pr = PullRequest.get(pull_request_id)
62 pr = PullRequest.get(pull_request_id)
63 pr.pull_request_state = pull_request.STATE_UPDATING
63 pr.pull_request_state = pull_request.STATE_UPDATING
64 Session().add(pr)
64 Session().add(pr)
65 Session().commit()
65 Session().commit()
66
66
67 id_, params = build_data(
67 id_, params = build_data(
68 self.apikey, 'merge_pull_request',
68 self.apikey, 'merge_pull_request',
69 repoid=pull_request_repo,
69 repoid=pull_request_repo,
70 pullrequestid=pull_request_id)
70 pullrequestid=pull_request_id)
71
71
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73 expected = 'Operation forbidden because pull request is in state {}, '\
73 expected = 'Operation forbidden because pull request is in state {}, '\
74 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING,
74 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING,
75 PullRequest.STATE_CREATED)
75 PullRequest.STATE_CREATED)
76 assert_error(id_, expected, given=response.body)
76 assert_error(id_, expected, given=response.body)
77
77
78 @pytest.mark.backends("git", "hg")
78 @pytest.mark.backends("git", "hg")
79 def test_api_merge_pull_request(self, pr_util, no_notifications):
79 def test_api_merge_pull_request(self, pr_util, no_notifications):
80 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
80 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
81 author = pull_request.user_id
81 author = pull_request.user_id
82 repo = pull_request.target_repo.repo_id
82 repo = pull_request.target_repo.repo_id
83 pull_request_id = pull_request.pull_request_id
83 pull_request_id = pull_request.pull_request_id
84 pull_request_repo = pull_request.target_repo.repo_name
84 pull_request_repo = pull_request.target_repo.repo_name
85
85
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey, 'comment_pull_request',
87 self.apikey, 'comment_pull_request',
88 repoid=pull_request_repo,
88 repoid=pull_request_repo,
89 pullrequestid=pull_request_id,
89 pullrequestid=pull_request_id,
90 status='approved')
90 status='approved')
91
91
92 response = api_call(self.app, params)
92 response = api_call(self.app, params)
93 expected = {
93 expected = {
94 'comment_id': response.json.get('result', {}).get('comment_id'),
94 'comment_id': response.json.get('result', {}).get('comment_id'),
95 'pull_request_id': pull_request_id,
95 'pull_request_id': pull_request_id,
96 'status': {'given': 'approved', 'was_changed': True}
96 'status': {'given': 'approved', 'was_changed': True}
97 }
97 }
98 assert_ok(id_, expected, given=response.body)
98 assert_ok(id_, expected, given=response.body)
99
99
100 id_, params = build_data(
100 id_, params = build_data(
101 self.apikey, 'merge_pull_request',
101 self.apikey, 'merge_pull_request',
102 repoid=pull_request_repo,
102 repoid=pull_request_repo,
103 pullrequestid=pull_request_id)
103 pullrequestid=pull_request_id)
104
104
105 response = api_call(self.app, params)
105 response = api_call(self.app, params)
106
106
107 pull_request = PullRequest.get(pull_request_id)
107 pull_request = PullRequest.get(pull_request_id)
108
108
109 expected = {
109 expected = {
110 'executed': True,
110 'executed': True,
111 'failure_reason': 0,
111 'failure_reason': 0,
112 'merge_status_message': 'This pull request can be automatically merged.',
112 'merge_status_message': 'This pull request can be automatically merged.',
113 'possible': True,
113 'possible': True,
114 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
114 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
115 'merge_ref': pull_request.shadow_merge_ref._asdict()
115 'merge_ref': pull_request.shadow_merge_ref._asdict()
116 }
116 }
117
117
118 assert_ok(id_, expected, response.body)
118 assert_ok(id_, expected, response.body)
119
119
120 journal = UserLog.query()\
120 journal = UserLog.query()\
121 .filter(UserLog.user_id == author)\
121 .filter(UserLog.user_id == author)\
122 .filter(UserLog.repository_id == repo) \
122 .filter(UserLog.repository_id == repo) \
123 .order_by(UserLog.user_log_id.asc()) \
123 .order_by(UserLog.user_log_id.asc()) \
124 .all()
124 .all()
125 assert journal[-2].action == 'repo.pull_request.merge'
125 assert journal[-2].action == 'repo.pull_request.merge'
126 assert journal[-1].action == 'repo.pull_request.close'
126 assert journal[-1].action == 'repo.pull_request.close'
127
127
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey, 'merge_pull_request',
129 self.apikey, 'merge_pull_request',
130 repoid=pull_request_repo, pullrequestid=pull_request_id)
130 repoid=pull_request_repo, pullrequestid=pull_request_id)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132
132
133 expected = 'merge not possible for following reasons: This pull request is closed.'
133 expected = 'merge not possible for following reasons: This pull request is closed.'
134 assert_error(id_, expected, given=response.body)
134 assert_error(id_, expected, given=response.body)
135
135
136 @pytest.mark.backends("git", "hg")
136 @pytest.mark.backends("git", "hg")
137 def test_api_merge_pull_request_as_another_user_no_perms_to_merge(
137 def test_api_merge_pull_request_as_another_user_no_perms_to_merge(
138 self, pr_util, no_notifications, user_util):
138 self, pr_util, no_notifications, user_util):
139 merge_user = user_util.create_user()
139 merge_user = user_util.create_user()
140 merge_user_id = merge_user.user_id
140 merge_user_id = merge_user.user_id
141 merge_user_username = merge_user.username
141 merge_user_username = merge_user.username
142
142
143 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
143 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
144
144
145 pull_request_id = pull_request.pull_request_id
145 pull_request_id = pull_request.pull_request_id
146 pull_request_repo = pull_request.target_repo.repo_name
146 pull_request_repo = pull_request.target_repo.repo_name
147
147
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 status='approved')
152 status='approved')
153
153
154 response = api_call(self.app, params)
154 response = api_call(self.app, params)
155 expected = {
155 expected = {
156 'comment_id': response.json.get('result', {}).get('comment_id'),
156 'comment_id': response.json.get('result', {}).get('comment_id'),
157 'pull_request_id': pull_request_id,
157 'pull_request_id': pull_request_id,
158 'status': {'given': 'approved', 'was_changed': True}
158 'status': {'given': 'approved', 'was_changed': True}
159 }
159 }
160 assert_ok(id_, expected, given=response.body)
160 assert_ok(id_, expected, given=response.body)
161 id_, params = build_data(
161 id_, params = build_data(
162 self.apikey, 'merge_pull_request',
162 self.apikey, 'merge_pull_request',
163 repoid=pull_request_repo,
163 repoid=pull_request_repo,
164 pullrequestid=pull_request_id,
164 pullrequestid=pull_request_id,
165 userid=merge_user_id
165 userid=merge_user_id
166 )
166 )
167
167
168 response = api_call(self.app, params)
168 response = api_call(self.app, params)
169 expected = 'merge not possible for following reasons: User `{}` ' \
169 expected = 'merge not possible for following reasons: User `{}` ' \
170 'not allowed to perform merge.'.format(merge_user_username)
170 'not allowed to perform merge.'.format(merge_user_username)
171 assert_error(id_, expected, response.body)
171 assert_error(id_, expected, response.body)
172
172
173 @pytest.mark.backends("git", "hg")
173 @pytest.mark.backends("git", "hg")
174 def test_api_merge_pull_request_as_another_user(self, pr_util, no_notifications, user_util):
174 def test_api_merge_pull_request_as_another_user(self, pr_util, no_notifications, user_util):
175 merge_user = user_util.create_user()
175 merge_user = user_util.create_user()
176 merge_user_id = merge_user.user_id
176 merge_user_id = merge_user.user_id
177 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
177 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
178 user_util.grant_user_permission_to_repo(
178 user_util.grant_user_permission_to_repo(
179 pull_request.target_repo, merge_user, 'repository.write')
179 pull_request.target_repo, merge_user, 'repository.write')
180 author = pull_request.user_id
180 author = pull_request.user_id
181 repo = pull_request.target_repo.repo_id
181 repo = pull_request.target_repo.repo_id
182 pull_request_id = pull_request.pull_request_id
182 pull_request_id = pull_request.pull_request_id
183 pull_request_repo = pull_request.target_repo.repo_name
183 pull_request_repo = pull_request.target_repo.repo_name
184
184
185 id_, params = build_data(
185 id_, params = build_data(
186 self.apikey, 'comment_pull_request',
186 self.apikey, 'comment_pull_request',
187 repoid=pull_request_repo,
187 repoid=pull_request_repo,
188 pullrequestid=pull_request_id,
188 pullrequestid=pull_request_id,
189 status='approved')
189 status='approved')
190
190
191 response = api_call(self.app, params)
191 response = api_call(self.app, params)
192 expected = {
192 expected = {
193 'comment_id': response.json.get('result', {}).get('comment_id'),
193 'comment_id': response.json.get('result', {}).get('comment_id'),
194 'pull_request_id': pull_request_id,
194 'pull_request_id': pull_request_id,
195 'status': {'given': 'approved', 'was_changed': True}
195 'status': {'given': 'approved', 'was_changed': True}
196 }
196 }
197 assert_ok(id_, expected, given=response.body)
197 assert_ok(id_, expected, given=response.body)
198
198
199 id_, params = build_data(
199 id_, params = build_data(
200 self.apikey, 'merge_pull_request',
200 self.apikey, 'merge_pull_request',
201 repoid=pull_request_repo,
201 repoid=pull_request_repo,
202 pullrequestid=pull_request_id,
202 pullrequestid=pull_request_id,
203 userid=merge_user_id
203 userid=merge_user_id
204 )
204 )
205
205
206 response = api_call(self.app, params)
206 response = api_call(self.app, params)
207
207
208 pull_request = PullRequest.get(pull_request_id)
208 pull_request = PullRequest.get(pull_request_id)
209
209
210 expected = {
210 expected = {
211 'executed': True,
211 'executed': True,
212 'failure_reason': 0,
212 'failure_reason': 0,
213 'merge_status_message': 'This pull request can be automatically merged.',
213 'merge_status_message': 'This pull request can be automatically merged.',
214 'possible': True,
214 'possible': True,
215 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
215 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
216 'merge_ref': pull_request.shadow_merge_ref._asdict()
216 'merge_ref': pull_request.shadow_merge_ref._asdict()
217 }
217 }
218
218
219 assert_ok(id_, expected, response.body)
219 assert_ok(id_, expected, response.body)
220
220
221 journal = UserLog.query() \
221 journal = UserLog.query() \
222 .filter(UserLog.user_id == merge_user_id) \
222 .filter(UserLog.user_id == merge_user_id) \
223 .filter(UserLog.repository_id == repo) \
223 .filter(UserLog.repository_id == repo) \
224 .order_by(UserLog.user_log_id.asc()) \
224 .order_by(UserLog.user_log_id.asc()) \
225 .all()
225 .all()
226 assert journal[-2].action == 'repo.pull_request.merge'
226 assert journal[-2].action == 'repo.pull_request.merge'
227 assert journal[-1].action == 'repo.pull_request.close'
227 assert journal[-1].action == 'repo.pull_request.close'
228
228
229 id_, params = build_data(
229 id_, params = build_data(
230 self.apikey, 'merge_pull_request',
230 self.apikey, 'merge_pull_request',
231 repoid=pull_request_repo, pullrequestid=pull_request_id, userid=merge_user_id)
231 repoid=pull_request_repo, pullrequestid=pull_request_id, userid=merge_user_id)
232 response = api_call(self.app, params)
232 response = api_call(self.app, params)
233
233
234 expected = 'merge not possible for following reasons: This pull request is closed.'
234 expected = 'merge not possible for following reasons: This pull request is closed.'
235 assert_error(id_, expected, given=response.body)
235 assert_error(id_, expected, given=response.body)
236
236
237 @pytest.mark.backends("git", "hg")
237 @pytest.mark.backends("git", "hg")
238 def test_api_merge_pull_request_repo_error(self, pr_util):
238 def test_api_merge_pull_request_repo_error(self, pr_util):
239 pull_request = pr_util.create_pull_request()
239 pull_request = pr_util.create_pull_request()
240 id_, params = build_data(
240 id_, params = build_data(
241 self.apikey, 'merge_pull_request',
241 self.apikey, 'merge_pull_request',
242 repoid=666, pullrequestid=pull_request.pull_request_id)
242 repoid=666, pullrequestid=pull_request.pull_request_id)
243 response = api_call(self.app, params)
243 response = api_call(self.app, params)
244
244
245 expected = 'repository `666` does not exist'
245 expected = 'repository `666` does not exist'
246 assert_error(id_, expected, given=response.body)
246 assert_error(id_, expected, given=response.body)
247
247
248 @pytest.mark.backends("git", "hg")
248 @pytest.mark.backends("git", "hg")
249 def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util):
249 def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util):
250 pull_request = pr_util.create_pull_request(mergeable=True)
250 pull_request = pr_util.create_pull_request(mergeable=True)
251 id_, params = build_data(
251 id_, params = build_data(
252 self.apikey_regular, 'merge_pull_request',
252 self.apikey_regular, 'merge_pull_request',
253 repoid=pull_request.target_repo.repo_name,
253 repoid=pull_request.target_repo.repo_name,
254 pullrequestid=pull_request.pull_request_id,
254 pullrequestid=pull_request.pull_request_id,
255 userid=TEST_USER_ADMIN_LOGIN)
255 userid=TEST_USER_ADMIN_LOGIN)
256 response = api_call(self.app, params)
256 response = api_call(self.app, params)
257
257
258 expected = 'userid is not the same as your user'
258 expected = 'userid is not the same as your user'
259 assert_error(id_, expected, given=response.body)
259 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import mock
22 import mock
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
32
33 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
34 def test_api_pull(self, backend):
34 def test_api_pull(self, backend):
35 r = backend.create_repo()
35 r = backend.create_repo()
36 repo_name = r.repo_name
36 repo_name = r.repo_name
37 clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
37 clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
38 r.clone_uri = clone_uri
38 r.clone_uri = clone_uri
39
39
40 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
40 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
41 with mock.patch('rhodecode.model.scm.url_validator'):
41 with mock.patch('rhodecode.model.scm.url_validator'):
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43 msg = 'Pulled from url `%s` on repo `%s`' % (
43 msg = 'Pulled from url `%s` on repo `%s`' % (
44 clone_uri, repo_name)
44 clone_uri, repo_name)
45 expected = {'msg': msg,
45 expected = {'msg': msg,
46 'repository': repo_name}
46 'repository': repo_name}
47 assert_ok(id_, expected, given=response.body)
47 assert_ok(id_, expected, given=response.body)
48
48
49 def test_api_pull_error(self, backend):
49 def test_api_pull_error(self, backend):
50 id_, params = build_data(
50 id_, params = build_data(
51 self.apikey, 'pull', repoid=backend.repo_name)
51 self.apikey, 'pull', repoid=backend.repo_name)
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 expected = 'Unable to pull changes from `None`'
54 expected = 'Unable to pull changes from `None`'
55 assert_error(id_, expected, given=response.body)
55 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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,59 +1,59 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, assert_error
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, assert_error
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestStoreException(object):
28 class TestStoreException(object):
29
29
30 def test_store_exception_invalid_json(self):
30 def test_store_exception_invalid_json(self):
31 id_, params = build_data(self.apikey, 'store_exception',
31 id_, params = build_data(self.apikey, 'store_exception',
32 exc_data_json='XXX,{')
32 exc_data_json='XXX,{')
33 response = api_call(self.app, params)
33 response = api_call(self.app, params)
34
34
35 expected = 'Failed to parse JSON data from exc_data_json field. ' \
35 expected = 'Failed to parse JSON data from exc_data_json field. ' \
36 'Please make sure it contains a valid JSON.'
36 'Please make sure it contains a valid JSON.'
37 assert_error(id_, expected, given=response.body)
37 assert_error(id_, expected, given=response.body)
38
38
39 def test_store_exception_missing_json_params_json(self):
39 def test_store_exception_missing_json_params_json(self):
40 id_, params = build_data(self.apikey, 'store_exception',
40 id_, params = build_data(self.apikey, 'store_exception',
41 exc_data_json='{"foo":"bar"}')
41 exc_data_json='{"foo":"bar"}')
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43
43
44 expected = "Missing exc_traceback, or exc_type_name in " \
44 expected = "Missing exc_traceback, or exc_type_name in " \
45 "exc_data_json field. Missing: 'exc_traceback'"
45 "exc_data_json field. Missing: 'exc_traceback'"
46 assert_error(id_, expected, given=response.body)
46 assert_error(id_, expected, given=response.body)
47
47
48 def test_store_exception(self):
48 def test_store_exception(self):
49 id_, params = build_data(
49 id_, params = build_data(
50 self.apikey, 'store_exception',
50 self.apikey, 'store_exception',
51 exc_data_json='{"exc_traceback": "invalid", "exc_type_name":"ValueError"}')
51 exc_data_json='{"exc_traceback": "invalid", "exc_type_name":"ValueError"}')
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53 exc_id = response.json['result']['exc_id']
53 exc_id = response.json['result']['exc_id']
54
54
55 expected = {
55 expected = {
56 'exc_id': exc_id,
56 'exc_id': exc_id,
57 'exc_url': 'http://example.com/_admin/settings/exceptions/{}'.format(exc_id)
57 'exc_url': 'http://example.com/_admin/settings/exceptions/{}'.format(exc_id)
58 }
58 }
59 assert_ok(id_, expected, given=response.body)
59 assert_ok(id_, expected, given=response.body)
@@ -1,212 +1,212 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.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
92
93 added_commit_id = commits[-1].raw_id # c commit
93 added_commit_id = commits[-1].raw_id # c commit
94 common_commit_id = commits[1].raw_id # b commit is common ancestor
94 common_commit_id = commits[1].raw_id # b commit is common ancestor
95 total_commits = [added_commit_id, common_commit_id]
95 total_commits = [added_commit_id, common_commit_id]
96
96
97 id_, params = build_data(
97 id_, params = build_data(
98 self.apikey, 'update_pull_request',
98 self.apikey, 'update_pull_request',
99 repoid=pull_request.target_repo.repo_name,
99 repoid=pull_request.target_repo.repo_name,
100 pullrequestid=pull_request.pull_request_id,
100 pullrequestid=pull_request.pull_request_id,
101 update_commits=True
101 update_commits=True
102 )
102 )
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104
104
105 expected = {
105 expected = {
106 "msg": "Updated pull request `{}`".format(
106 "msg": "Updated pull request `{}`".format(
107 pull_request.pull_request_id),
107 pull_request.pull_request_id),
108 "pull_request": response.json['result']['pull_request'],
108 "pull_request": response.json['result']['pull_request'],
109 "updated_commits": {"added": [added_commit_id],
109 "updated_commits": {"added": [added_commit_id],
110 "common": [common_commit_id],
110 "common": [common_commit_id],
111 "total": total_commits,
111 "total": total_commits,
112 "removed": []},
112 "removed": []},
113 "updated_reviewers": {"added": [], "removed": []},
113 "updated_reviewers": {"added": [], "removed": []},
114 }
114 }
115
115
116 assert_ok(id_, expected, response.body)
116 assert_ok(id_, expected, response.body)
117
117
118 @pytest.mark.backends("git", "hg")
118 @pytest.mark.backends("git", "hg")
119 def test_api_update_change_reviewers(
119 def test_api_update_change_reviewers(
120 self, user_util, pr_util, no_notifications):
120 self, user_util, pr_util, no_notifications):
121 a = user_util.create_user()
121 a = user_util.create_user()
122 b = user_util.create_user()
122 b = user_util.create_user()
123 c = user_util.create_user()
123 c = user_util.create_user()
124 new_reviewers = [
124 new_reviewers = [
125 {'username': b.username,'reasons': ['updated via API'],
125 {'username': b.username,'reasons': ['updated via API'],
126 'mandatory':False},
126 'mandatory':False},
127 {'username': c.username, 'reasons': ['updated via API'],
127 {'username': c.username, 'reasons': ['updated via API'],
128 'mandatory':False},
128 'mandatory':False},
129 ]
129 ]
130
130
131 added = [b.username, c.username]
131 added = [b.username, c.username]
132 removed = [a.username]
132 removed = [a.username]
133
133
134 pull_request = pr_util.create_pull_request(
134 pull_request = pr_util.create_pull_request(
135 reviewers=[(a.username, ['added via API'], False, [])])
135 reviewers=[(a.username, ['added via API'], False, [])])
136
136
137 id_, params = build_data(
137 id_, params = build_data(
138 self.apikey, 'update_pull_request',
138 self.apikey, 'update_pull_request',
139 repoid=pull_request.target_repo.repo_name,
139 repoid=pull_request.target_repo.repo_name,
140 pullrequestid=pull_request.pull_request_id,
140 pullrequestid=pull_request.pull_request_id,
141 reviewers=new_reviewers)
141 reviewers=new_reviewers)
142 response = api_call(self.app, params)
142 response = api_call(self.app, params)
143 expected = {
143 expected = {
144 "msg": "Updated pull request `{}`".format(
144 "msg": "Updated pull request `{}`".format(
145 pull_request.pull_request_id),
145 pull_request.pull_request_id),
146 "pull_request": response.json['result']['pull_request'],
146 "pull_request": response.json['result']['pull_request'],
147 "updated_commits": {"added": [], "common": [], "removed": []},
147 "updated_commits": {"added": [], "common": [], "removed": []},
148 "updated_reviewers": {"added": added, "removed": removed},
148 "updated_reviewers": {"added": added, "removed": removed},
149 }
149 }
150
150
151 assert_ok(id_, expected, response.body)
151 assert_ok(id_, expected, response.body)
152
152
153 @pytest.mark.backends("git", "hg")
153 @pytest.mark.backends("git", "hg")
154 def test_api_update_bad_user_in_reviewers(self, pr_util):
154 def test_api_update_bad_user_in_reviewers(self, pr_util):
155 pull_request = pr_util.create_pull_request()
155 pull_request = pr_util.create_pull_request()
156
156
157 id_, params = build_data(
157 id_, params = build_data(
158 self.apikey, 'update_pull_request',
158 self.apikey, 'update_pull_request',
159 repoid=pull_request.target_repo.repo_name,
159 repoid=pull_request.target_repo.repo_name,
160 pullrequestid=pull_request.pull_request_id,
160 pullrequestid=pull_request.pull_request_id,
161 reviewers=[{'username': 'bad_name'}])
161 reviewers=[{'username': 'bad_name'}])
162 response = api_call(self.app, params)
162 response = api_call(self.app, params)
163
163
164 expected = 'user `bad_name` does not exist'
164 expected = 'user `bad_name` does not exist'
165
165
166 assert_error(id_, expected, response.body)
166 assert_error(id_, expected, response.body)
167
167
168 @pytest.mark.backends("git", "hg")
168 @pytest.mark.backends("git", "hg")
169 def test_api_update_repo_error(self, pr_util):
169 def test_api_update_repo_error(self, pr_util):
170 pull_request = pr_util.create_pull_request()
170 pull_request = pr_util.create_pull_request()
171 id_, params = build_data(
171 id_, params = build_data(
172 self.apikey, 'update_pull_request',
172 self.apikey, 'update_pull_request',
173 repoid='fake',
173 repoid='fake',
174 pullrequestid=pull_request.pull_request_id,
174 pullrequestid=pull_request.pull_request_id,
175 reviewers=[{'username': 'bad_name'}])
175 reviewers=[{'username': 'bad_name'}])
176 response = api_call(self.app, params)
176 response = api_call(self.app, params)
177
177
178 expected = 'repository `fake` does not exist'
178 expected = 'repository `fake` does not exist'
179
179
180 response_json = response.json['error']
180 response_json = response.json['error']
181 assert response_json == expected
181 assert response_json == expected
182
182
183 @pytest.mark.backends("git", "hg")
183 @pytest.mark.backends("git", "hg")
184 def test_api_update_pull_request_error(self, pr_util):
184 def test_api_update_pull_request_error(self, pr_util):
185 pull_request = pr_util.create_pull_request()
185 pull_request = pr_util.create_pull_request()
186
186
187 id_, params = build_data(
187 id_, params = build_data(
188 self.apikey, 'update_pull_request',
188 self.apikey, 'update_pull_request',
189 repoid=pull_request.target_repo.repo_name,
189 repoid=pull_request.target_repo.repo_name,
190 pullrequestid=999999,
190 pullrequestid=999999,
191 reviewers=[{'username': 'bad_name'}])
191 reviewers=[{'username': 'bad_name'}])
192 response = api_call(self.app, params)
192 response = api_call(self.app, params)
193
193
194 expected = 'pull request `999999` does not exist'
194 expected = 'pull request `999999` does not exist'
195 assert_error(id_, expected, response.body)
195 assert_error(id_, expected, response.body)
196
196
197 @pytest.mark.backends("git", "hg")
197 @pytest.mark.backends("git", "hg")
198 def test_api_update_pull_request_no_perms_to_update(
198 def test_api_update_pull_request_no_perms_to_update(
199 self, user_util, pr_util):
199 self, user_util, pr_util):
200 user = user_util.create_user()
200 user = user_util.create_user()
201 pull_request = pr_util.create_pull_request()
201 pull_request = pr_util.create_pull_request()
202
202
203 id_, params = build_data(
203 id_, params = build_data(
204 user.api_key, 'update_pull_request',
204 user.api_key, 'update_pull_request',
205 repoid=pull_request.target_repo.repo_name,
205 repoid=pull_request.target_repo.repo_name,
206 pullrequestid=pull_request.pull_request_id,)
206 pullrequestid=pull_request.pull_request_id,)
207 response = api_call(self.app, params)
207 response = api_call(self.app, params)
208
208
209 expected = ('pull request `%s` update failed, '
209 expected = ('pull request `%s` update failed, '
210 'no permission to update.') % pull_request.pull_request_id
210 'no permission to update.') % pull_request.pull_request_id
211
211
212 assert_error(id_, expected, response.body)
212 assert_error(id_, expected, response.body)
@@ -1,203 +1,203 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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 plain_http_host_only_stub
29 from rhodecode.tests.plugin import plain_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 ({'clone_uri': 'http://example.com/repo_pull'},
59 ({'clone_uri': 'http://example.com/repo_pull'},
60 {'clone_uri': 'http://example.com/repo_pull'}),
60 {'clone_uri': 'http://example.com/repo_pull'}),
61
61
62 ({'push_uri': ''},
62 ({'push_uri': ''},
63 {'push_uri': ''}),
63 {'push_uri': ''}),
64
64
65 ({'push_uri': 'http://example.com/repo_push'},
65 ({'push_uri': 'http://example.com/repo_push'},
66 {'push_uri': 'http://example.com/repo_push'}),
66 {'push_uri': 'http://example.com/repo_push'}),
67
67
68 ({'landing_rev': 'rev:tip'},
68 ({'landing_rev': 'rev:tip'},
69 {'landing_rev': ['rev', 'tip']}),
69 {'landing_rev': ['rev', 'tip']}),
70
70
71 ({'enable_statistics': True},
71 ({'enable_statistics': True},
72 SAME_AS_UPDATES),
72 SAME_AS_UPDATES),
73
73
74 ({'enable_locking': True},
74 ({'enable_locking': True},
75 SAME_AS_UPDATES),
75 SAME_AS_UPDATES),
76
76
77 ({'enable_downloads': True},
77 ({'enable_downloads': True},
78 SAME_AS_UPDATES),
78 SAME_AS_UPDATES),
79
79
80 ({'repo_name': 'new_repo_name'},
80 ({'repo_name': 'new_repo_name'},
81 {
81 {
82 'repo_name': 'new_repo_name',
82 'repo_name': 'new_repo_name',
83 'url': 'http://{}/new_repo_name'.format(plain_http_host_only_stub())
83 'url': 'http://{}/new_repo_name'.format(plain_http_host_only_stub())
84 }),
84 }),
85
85
86 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
86 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
87 '_group': 'test_group_for_update'},
87 '_group': 'test_group_for_update'},
88 {
88 {
89 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
89 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
90 'url': 'http://{}/test_group_for_update/{}'.format(
90 'url': 'http://{}/test_group_for_update/{}'.format(
91 plain_http_host_only_stub(), UPDATE_REPO_NAME)
91 plain_http_host_only_stub(), UPDATE_REPO_NAME)
92 }),
92 }),
93 ])
93 ])
94 def test_api_update_repo(self, updates, expected, backend):
94 def test_api_update_repo(self, updates, expected, backend):
95 repo_name = UPDATE_REPO_NAME
95 repo_name = UPDATE_REPO_NAME
96 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
96 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
97 if updates.get('_group'):
97 if updates.get('_group'):
98 fixture.create_repo_group(updates['_group'])
98 fixture.create_repo_group(updates['_group'])
99
99
100 expected_api_data = repo.get_api_data(include_secrets=True)
100 expected_api_data = repo.get_api_data(include_secrets=True)
101 if expected is SAME_AS_UPDATES:
101 if expected is SAME_AS_UPDATES:
102 expected_api_data.update(updates)
102 expected_api_data.update(updates)
103 else:
103 else:
104 expected_api_data.update(expected)
104 expected_api_data.update(expected)
105
105
106 id_, params = build_data(
106 id_, params = build_data(
107 self.apikey, 'update_repo', repoid=repo_name, **updates)
107 self.apikey, 'update_repo', repoid=repo_name, **updates)
108
108
109 with mock.patch('rhodecode.model.validation_schema.validators.url_validator'):
109 with mock.patch('rhodecode.model.validation_schema.validators.url_validator'):
110 response = api_call(self.app, params)
110 response = api_call(self.app, params)
111
111
112 if updates.get('repo_name'):
112 if updates.get('repo_name'):
113 repo_name = updates['repo_name']
113 repo_name = updates['repo_name']
114
114
115 try:
115 try:
116 expected = {
116 expected = {
117 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
117 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
118 'repository': jsonify(expected_api_data)
118 'repository': jsonify(expected_api_data)
119 }
119 }
120 assert_ok(id_, expected, given=response.body)
120 assert_ok(id_, expected, given=response.body)
121 finally:
121 finally:
122 fixture.destroy_repo(repo_name)
122 fixture.destroy_repo(repo_name)
123 if updates.get('_group'):
123 if updates.get('_group'):
124 fixture.destroy_repo_group(updates['_group'])
124 fixture.destroy_repo_group(updates['_group'])
125
125
126 def test_api_update_repo_fork_of_field(self, backend):
126 def test_api_update_repo_fork_of_field(self, backend):
127 master_repo = backend.create_repo()
127 master_repo = backend.create_repo()
128 repo = backend.create_repo()
128 repo = backend.create_repo()
129 updates = {
129 updates = {
130 'fork_of': master_repo.repo_name,
130 'fork_of': master_repo.repo_name,
131 'fork_of_id': master_repo.repo_id
131 'fork_of_id': master_repo.repo_id
132 }
132 }
133 expected_api_data = repo.get_api_data(include_secrets=True)
133 expected_api_data = repo.get_api_data(include_secrets=True)
134 expected_api_data.update(updates)
134 expected_api_data.update(updates)
135
135
136 id_, params = build_data(
136 id_, params = build_data(
137 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
137 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
138 response = api_call(self.app, params)
138 response = api_call(self.app, params)
139 expected = {
139 expected = {
140 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
140 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
141 'repository': jsonify(expected_api_data)
141 'repository': jsonify(expected_api_data)
142 }
142 }
143 assert_ok(id_, expected, given=response.body)
143 assert_ok(id_, expected, given=response.body)
144 result = response.json['result']['repository']
144 result = response.json['result']['repository']
145 assert result['fork_of'] == master_repo.repo_name
145 assert result['fork_of'] == master_repo.repo_name
146 assert result['fork_of_id'] == master_repo.repo_id
146 assert result['fork_of_id'] == master_repo.repo_id
147
147
148 def test_api_update_repo_fork_of_not_found(self, backend):
148 def test_api_update_repo_fork_of_not_found(self, backend):
149 master_repo_name = 'fake-parent-repo'
149 master_repo_name = 'fake-parent-repo'
150 repo = backend.create_repo()
150 repo = backend.create_repo()
151 updates = {
151 updates = {
152 'fork_of': master_repo_name
152 'fork_of': master_repo_name
153 }
153 }
154 id_, params = build_data(
154 id_, params = build_data(
155 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
155 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
156 response = api_call(self.app, params)
156 response = api_call(self.app, params)
157 expected = {
157 expected = {
158 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
158 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
159 master_repo_name)}
159 master_repo_name)}
160 assert_error(id_, expected, given=response.body)
160 assert_error(id_, expected, given=response.body)
161
161
162 def test_api_update_repo_with_repo_group_not_existing(self):
162 def test_api_update_repo_with_repo_group_not_existing(self):
163 repo_name = 'admin_owned'
163 repo_name = 'admin_owned'
164 fake_repo_group = 'test_group_for_update'
164 fake_repo_group = 'test_group_for_update'
165 fixture.create_repo(repo_name)
165 fixture.create_repo(repo_name)
166 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
166 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
167 id_, params = build_data(
167 id_, params = build_data(
168 self.apikey, 'update_repo', repoid=repo_name, **updates)
168 self.apikey, 'update_repo', repoid=repo_name, **updates)
169 response = api_call(self.app, params)
169 response = api_call(self.app, params)
170 try:
170 try:
171 expected = {
171 expected = {
172 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
172 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
173 }
173 }
174 assert_error(id_, expected, given=response.body)
174 assert_error(id_, expected, given=response.body)
175 finally:
175 finally:
176 fixture.destroy_repo(repo_name)
176 fixture.destroy_repo(repo_name)
177
177
178 def test_api_update_repo_regular_user_not_allowed(self):
178 def test_api_update_repo_regular_user_not_allowed(self):
179 repo_name = 'admin_owned'
179 repo_name = 'admin_owned'
180 fixture.create_repo(repo_name)
180 fixture.create_repo(repo_name)
181 updates = {'active': False}
181 updates = {'active': False}
182 id_, params = build_data(
182 id_, params = build_data(
183 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
183 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
184 response = api_call(self.app, params)
184 response = api_call(self.app, params)
185 try:
185 try:
186 expected = 'repository `%s` does not exist' % (repo_name,)
186 expected = 'repository `%s` does not exist' % (repo_name,)
187 assert_error(id_, expected, given=response.body)
187 assert_error(id_, expected, given=response.body)
188 finally:
188 finally:
189 fixture.destroy_repo(repo_name)
189 fixture.destroy_repo(repo_name)
190
190
191 @mock.patch.object(RepoModel, 'update', crash)
191 @mock.patch.object(RepoModel, 'update', crash)
192 def test_api_update_repo_exception_occurred(self, backend):
192 def test_api_update_repo_exception_occurred(self, backend):
193 repo_name = UPDATE_REPO_NAME
193 repo_name = UPDATE_REPO_NAME
194 fixture.create_repo(repo_name, repo_type=backend.alias)
194 fixture.create_repo(repo_name, repo_type=backend.alias)
195 id_, params = build_data(
195 id_, params = build_data(
196 self.apikey, 'update_repo', repoid=repo_name,
196 self.apikey, 'update_repo', repoid=repo_name,
197 owner=TEST_USER_ADMIN_LOGIN,)
197 owner=TEST_USER_ADMIN_LOGIN,)
198 response = api_call(self.app, params)
198 response = api_call(self.app, params)
199 try:
199 try:
200 expected = 'failed to update repo `%s`' % (repo_name,)
200 expected = 'failed to update repo `%s`' % (repo_name,)
201 assert_error(id_, expected, given=response.body)
201 assert_error(id_, expected, given=response.body)
202 finally:
202 finally:
203 fixture.destroy_repo(repo_name)
203 fixture.destroy_repo(repo_name)
@@ -1,150 +1,150 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import 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,121 +1,121 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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 ('description', 'CTO 4 Life')
46 ('description', 'CTO 4 Life')
47 ])
47 ])
48 def test_api_update_user(self, name, expected, user_util):
48 def test_api_update_user(self, name, expected, user_util):
49 usr = user_util.create_user()
49 usr = user_util.create_user()
50
50
51 kw = {name: expected, 'userid': usr.user_id}
51 kw = {name: expected, 'userid': usr.user_id}
52 id_, params = build_data(self.apikey, 'update_user', **kw)
52 id_, params = build_data(self.apikey, 'update_user', **kw)
53 response = api_call(self.app, params)
53 response = api_call(self.app, params)
54
54
55 ret = {
55 ret = {
56 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
56 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
57 'user': jsonify(
57 'user': jsonify(
58 UserModel()
58 UserModel()
59 .get_by_username(usr.username)
59 .get_by_username(usr.username)
60 .get_api_data(include_secrets=True)
60 .get_api_data(include_secrets=True)
61 )
61 )
62 }
62 }
63
63
64 expected = ret
64 expected = ret
65 assert_ok(id_, expected, given=response.body)
65 assert_ok(id_, expected, given=response.body)
66
66
67 def test_api_update_user_no_changed_params(self):
67 def test_api_update_user_no_changed_params(self):
68 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
68 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
69 ret = jsonify(usr.get_api_data(include_secrets=True))
69 ret = jsonify(usr.get_api_data(include_secrets=True))
70 id_, params = build_data(
70 id_, params = build_data(
71 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
71 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
72
72
73 response = api_call(self.app, params)
73 response = api_call(self.app, params)
74 ret = {
74 ret = {
75 'msg': 'updated user ID:%s %s' % (
75 'msg': 'updated user ID:%s %s' % (
76 usr.user_id, TEST_USER_ADMIN_LOGIN),
76 usr.user_id, TEST_USER_ADMIN_LOGIN),
77 'user': ret
77 'user': ret
78 }
78 }
79 expected = ret
79 expected = ret
80 expected['user']['last_activity'] = response.json['result']['user'][
80 expected['user']['last_activity'] = response.json['result']['user'][
81 'last_activity']
81 'last_activity']
82 assert_ok(id_, expected, given=response.body)
82 assert_ok(id_, expected, given=response.body)
83
83
84 def test_api_update_user_by_user_id(self):
84 def test_api_update_user_by_user_id(self):
85 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
85 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
86 ret = jsonify(usr.get_api_data(include_secrets=True))
86 ret = jsonify(usr.get_api_data(include_secrets=True))
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey, 'update_user', userid=usr.user_id)
88 self.apikey, 'update_user', userid=usr.user_id)
89
89
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91 ret = {
91 ret = {
92 'msg': 'updated user ID:%s %s' % (
92 'msg': 'updated user ID:%s %s' % (
93 usr.user_id, TEST_USER_ADMIN_LOGIN),
93 usr.user_id, TEST_USER_ADMIN_LOGIN),
94 'user': ret
94 'user': ret
95 }
95 }
96 expected = ret
96 expected = ret
97 expected['user']['last_activity'] = response.json['result']['user'][
97 expected['user']['last_activity'] = response.json['result']['user'][
98 'last_activity']
98 'last_activity']
99 assert_ok(id_, expected, given=response.body)
99 assert_ok(id_, expected, given=response.body)
100
100
101 def test_api_update_user_default_user(self):
101 def test_api_update_user_default_user(self):
102 usr = User.get_default_user()
102 usr = User.get_default_user()
103 id_, params = build_data(
103 id_, params = build_data(
104 self.apikey, 'update_user', userid=usr.user_id)
104 self.apikey, 'update_user', userid=usr.user_id)
105
105
106 response = api_call(self.app, params)
106 response = api_call(self.app, params)
107 expected = 'editing default user is forbidden'
107 expected = 'editing default user is forbidden'
108 assert_error(id_, expected, given=response.body)
108 assert_error(id_, expected, given=response.body)
109
109
110 @mock.patch.object(UserModel, 'update_user', crash)
110 @mock.patch.object(UserModel, 'update_user', crash)
111 def test_api_update_user_when_exception_happens(self):
111 def test_api_update_user_when_exception_happens(self):
112 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
112 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
113 ret = jsonify(usr.get_api_data(include_secrets=True))
113 ret = jsonify(usr.get_api_data(include_secrets=True))
114 id_, params = build_data(
114 id_, params = build_data(
115 self.apikey, 'update_user', userid=usr.user_id)
115 self.apikey, 'update_user', userid=usr.user_id)
116
116
117 response = api_call(self.app, params)
117 response = api_call(self.app, params)
118 ret = 'failed to update user `%s`' % (usr.user_id,)
118 ret = 'failed to update user `%s`' % (usr.user_id,)
119
119
120 expected = ret
120 expected = ret
121 assert_error(id_, expected, given=response.body)
121 assert_error(id_, expected, given=response.body)
@@ -1,125 +1,125 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
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 ('sync', {'sync': False}),
40 ('sync', {'sync': False}),
41 ('sync', {'sync': True})
41 ('sync', {'sync': True})
42 ])
42 ])
43 def test_api_update_user_group(self, changing_attr, updates, user_util):
43 def test_api_update_user_group(self, changing_attr, updates, user_util):
44 user_group = user_util.create_user_group()
44 user_group = user_util.create_user_group()
45 group_name = user_group.users_group_name
45 group_name = user_group.users_group_name
46 expected_api_data = user_group.get_api_data()
46 expected_api_data = user_group.get_api_data()
47 expected_api_data.update(updates)
47 expected_api_data.update(updates)
48
48
49 id_, params = build_data(
49 id_, params = build_data(
50 self.apikey, 'update_user_group', usergroupid=group_name,
50 self.apikey, 'update_user_group', usergroupid=group_name,
51 **updates)
51 **updates)
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 # special case for sync
54 # special case for sync
55 if changing_attr == 'sync' and updates['sync'] is False:
55 if changing_attr == 'sync' and updates['sync'] is False:
56 expected_api_data['sync'] = None
56 expected_api_data['sync'] = None
57 elif changing_attr == 'sync' and updates['sync'] is True:
57 elif changing_attr == 'sync' and updates['sync'] is True:
58 expected_api_data['sync'] = 'manual_api'
58 expected_api_data['sync'] = 'manual_api'
59
59
60 expected = {
60 expected = {
61 'msg': 'updated user group ID:%s %s' % (
61 'msg': 'updated user group ID:%s %s' % (
62 user_group.users_group_id, user_group.users_group_name),
62 user_group.users_group_id, user_group.users_group_name),
63 'user_group': jsonify(expected_api_data)
63 'user_group': jsonify(expected_api_data)
64 }
64 }
65 assert_ok(id_, expected, given=response.body)
65 assert_ok(id_, expected, given=response.body)
66
66
67 @pytest.mark.parametrize("changing_attr, updates", [
67 @pytest.mark.parametrize("changing_attr, updates", [
68 # TODO: mikhail: decide if we need to test against the commented params
68 # TODO: mikhail: decide if we need to test against the commented params
69 # ('group_name', {'group_name': 'new_group_name'}),
69 # ('group_name', {'group_name': 'new_group_name'}),
70 # ('group_name', {'group_name': 'test_group_for_update'}),
70 # ('group_name', {'group_name': 'test_group_for_update'}),
71 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
71 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
72 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
72 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
73 ('active', {'active': False}),
73 ('active', {'active': False}),
74 ('active', {'active': True}),
74 ('active', {'active': True}),
75 ('sync', {'sync': False}),
75 ('sync', {'sync': False}),
76 ('sync', {'sync': True})
76 ('sync', {'sync': True})
77 ])
77 ])
78 def test_api_update_user_group_regular_user(
78 def test_api_update_user_group_regular_user(
79 self, changing_attr, updates, user_util):
79 self, changing_attr, updates, user_util):
80 user_group = user_util.create_user_group()
80 user_group = user_util.create_user_group()
81 group_name = user_group.users_group_name
81 group_name = user_group.users_group_name
82 expected_api_data = user_group.get_api_data()
82 expected_api_data = user_group.get_api_data()
83 expected_api_data.update(updates)
83 expected_api_data.update(updates)
84
84
85 # grant permission to this user
85 # grant permission to this user
86 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
86 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
87
87
88 user_util.grant_user_permission_to_user_group(
88 user_util.grant_user_permission_to_user_group(
89 user_group, user, 'usergroup.admin')
89 user_group, user, 'usergroup.admin')
90 id_, params = build_data(
90 id_, params = build_data(
91 self.apikey_regular, 'update_user_group',
91 self.apikey_regular, 'update_user_group',
92 usergroupid=group_name, **updates)
92 usergroupid=group_name, **updates)
93 response = api_call(self.app, params)
93 response = api_call(self.app, params)
94 # special case for sync
94 # special case for sync
95 if changing_attr == 'sync' and updates['sync'] is False:
95 if changing_attr == 'sync' and updates['sync'] is False:
96 expected_api_data['sync'] = None
96 expected_api_data['sync'] = None
97 elif changing_attr == 'sync' and updates['sync'] is True:
97 elif changing_attr == 'sync' and updates['sync'] is True:
98 expected_api_data['sync'] = 'manual_api'
98 expected_api_data['sync'] = 'manual_api'
99
99
100 expected = {
100 expected = {
101 'msg': 'updated user group ID:%s %s' % (
101 'msg': 'updated user group ID:%s %s' % (
102 user_group.users_group_id, user_group.users_group_name),
102 user_group.users_group_id, user_group.users_group_name),
103 'user_group': jsonify(expected_api_data)
103 'user_group': jsonify(expected_api_data)
104 }
104 }
105 assert_ok(id_, expected, given=response.body)
105 assert_ok(id_, expected, given=response.body)
106
106
107 def test_api_update_user_group_regular_user_no_permission(self, user_util):
107 def test_api_update_user_group_regular_user_no_permission(self, user_util):
108 user_group = user_util.create_user_group()
108 user_group = user_util.create_user_group()
109 group_name = user_group.users_group_name
109 group_name = user_group.users_group_name
110 id_, params = build_data(
110 id_, params = build_data(
111 self.apikey_regular, 'update_user_group', usergroupid=group_name)
111 self.apikey_regular, 'update_user_group', usergroupid=group_name)
112 response = api_call(self.app, params)
112 response = api_call(self.app, params)
113
113
114 expected = 'user group `%s` does not exist' % (group_name)
114 expected = 'user group `%s` does not exist' % (group_name)
115 assert_error(id_, expected, given=response.body)
115 assert_error(id_, expected, given=response.body)
116
116
117 @mock.patch.object(UserGroupModel, 'update', crash)
117 @mock.patch.object(UserGroupModel, 'update', crash)
118 def test_api_update_user_group_exception_occurred(self, user_util):
118 def test_api_update_user_group_exception_occurred(self, user_util):
119 user_group = user_util.create_user_group()
119 user_group = user_util.create_user_group()
120 group_name = user_group.users_group_name
120 group_name = user_group.users_group_name
121 id_, params = build_data(
121 id_, params = build_data(
122 self.apikey, 'update_user_group', usergroupid=group_name)
122 self.apikey, 'update_user_group', usergroupid=group_name)
123 response = api_call(self.app, params)
123 response = api_call(self.app, params)
124 expected = 'failed to update user group `%s`' % (group_name,)
124 expected = 'failed to update user group `%s`' % (group_name,)
125 assert_error(id_, expected, given=response.body)
125 assert_error(id_, expected, given=response.body)
@@ -1,295 +1,295 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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 = 'bookmark:ref'
87 ref = 'bookmark: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 = (
90 expected_message = (
91 'The specified value:bookmark:`ref` does not exist, or is not allowed.')
91 'The specified value:bookmark:`ref` does not exist, or is not allowed.')
92 assert excinfo.value.message == expected_message
92 assert excinfo.value.message == expected_message
93
93
94 def test_branch_is_not_found(self):
94 def test_branch_is_not_found(self):
95 repo = Mock()
95 repo = Mock()
96 ref = 'branch:non-existing-one'
96 ref = 'branch:non-existing-one'
97 with patch('rhodecode.api.utils._get_ref_hash')\
97 with patch('rhodecode.api.utils._get_ref_hash')\
98 as _get_ref_hash:
98 as _get_ref_hash:
99 _get_ref_hash.side_effect = KeyError()
99 _get_ref_hash.side_effect = KeyError()
100 with pytest.raises(JSONRPCError) as excinfo:
100 with pytest.raises(JSONRPCError) as excinfo:
101 utils.resolve_ref_or_error(ref, repo)
101 utils.resolve_ref_or_error(ref, repo)
102 expected_message = (
102 expected_message = (
103 'The specified value:branch:`non-existing-one` does not exist, or is not allowed.')
103 'The specified value:branch:`non-existing-one` does not exist, or is not allowed.')
104 assert excinfo.value.message == expected_message
104 assert excinfo.value.message == expected_message
105
105
106 def test_bookmark_is_not_found(self):
106 def test_bookmark_is_not_found(self):
107 repo = Mock()
107 repo = Mock()
108 ref = 'bookmark:non-existing-one'
108 ref = 'bookmark:non-existing-one'
109 with patch('rhodecode.api.utils._get_ref_hash')\
109 with patch('rhodecode.api.utils._get_ref_hash')\
110 as _get_ref_hash:
110 as _get_ref_hash:
111 _get_ref_hash.side_effect = KeyError()
111 _get_ref_hash.side_effect = KeyError()
112 with pytest.raises(JSONRPCError) as excinfo:
112 with pytest.raises(JSONRPCError) as excinfo:
113 utils.resolve_ref_or_error(ref, repo)
113 utils.resolve_ref_or_error(ref, repo)
114 expected_message = (
114 expected_message = (
115 'The specified value:bookmark:`non-existing-one` does not exist, or is not allowed.')
115 'The specified value:bookmark:`non-existing-one` does not exist, or is not allowed.')
116 assert excinfo.value.message == expected_message
116 assert excinfo.value.message == expected_message
117
117
118 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
118 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
119 def test_ref_cannot_be_parsed(self, ref):
119 def test_ref_cannot_be_parsed(self, ref):
120 repo = Mock()
120 repo = Mock()
121 with pytest.raises(JSONRPCError) as excinfo:
121 with pytest.raises(JSONRPCError) as excinfo:
122 utils.resolve_ref_or_error(ref, repo)
122 utils.resolve_ref_or_error(ref, repo)
123 expected_message = (
123 expected_message = (
124 'Ref `{ref}` given in a wrong format. Please check the API'
124 'Ref `{ref}` given in a wrong format. Please check the API'
125 ' documentation for more details'.format(ref=ref)
125 ' documentation for more details'.format(ref=ref)
126 )
126 )
127 assert excinfo.value.message == expected_message
127 assert excinfo.value.message == expected_message
128
128
129
129
130 class TestGetRefHash(object):
130 class TestGetRefHash(object):
131 def setup(self):
131 def setup(self):
132 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
132 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
133 self.bookmark_name = 'test-bookmark'
133 self.bookmark_name = 'test-bookmark'
134
134
135 @pytest.mark.parametrize("alias, branch_name", [
135 @pytest.mark.parametrize("alias, branch_name", [
136 ("git", "master"),
136 ("git", "master"),
137 ("hg", "default")
137 ("hg", "default")
138 ])
138 ])
139 def test_returns_hash_by_branch_name(self, alias, branch_name):
139 def test_returns_hash_by_branch_name(self, alias, branch_name):
140 with patch('rhodecode.model.db.Repository') as repo:
140 with patch('rhodecode.model.db.Repository') as repo:
141 repo.scm_instance().alias = alias
141 repo.scm_instance().alias = alias
142 repo.scm_instance().branches = {branch_name: self.commit_hash}
142 repo.scm_instance().branches = {branch_name: self.commit_hash}
143 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
143 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
144 assert result_hash == self.commit_hash
144 assert result_hash == self.commit_hash
145
145
146 @pytest.mark.parametrize("alias, branch_name", [
146 @pytest.mark.parametrize("alias, branch_name", [
147 ("git", "master"),
147 ("git", "master"),
148 ("hg", "default")
148 ("hg", "default")
149 ])
149 ])
150 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
150 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
151 with patch('rhodecode.model.db.Repository') as repo:
151 with patch('rhodecode.model.db.Repository') as repo:
152 repo.scm_instance().alias = alias
152 repo.scm_instance().alias = alias
153 repo.scm_instance().branches = {}
153 repo.scm_instance().branches = {}
154 with pytest.raises(KeyError):
154 with pytest.raises(KeyError):
155 utils._get_ref_hash(repo, 'branch', branch_name)
155 utils._get_ref_hash(repo, 'branch', branch_name)
156
156
157 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
157 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
158 with patch('rhodecode.model.db.Repository') as repo:
158 with patch('rhodecode.model.db.Repository') as repo:
159 repo.scm_instance().alias = 'hg'
159 repo.scm_instance().alias = 'hg'
160 repo.scm_instance().bookmarks = {
160 repo.scm_instance().bookmarks = {
161 self.bookmark_name: self.commit_hash}
161 self.bookmark_name: self.commit_hash}
162 result_hash = utils._get_ref_hash(
162 result_hash = utils._get_ref_hash(
163 repo, 'bookmark', self.bookmark_name)
163 repo, 'bookmark', self.bookmark_name)
164 assert result_hash == self.commit_hash
164 assert result_hash == self.commit_hash
165
165
166 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
166 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
167 with patch('rhodecode.model.db.Repository') as repo:
167 with patch('rhodecode.model.db.Repository') as repo:
168 repo.scm_instance().alias = 'hg'
168 repo.scm_instance().alias = 'hg'
169 repo.scm_instance().bookmarks = {}
169 repo.scm_instance().bookmarks = {}
170 with pytest.raises(KeyError):
170 with pytest.raises(KeyError):
171 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
171 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
172
172
173 def test_raises_error_when_bookmark_is_specified_for_git(self):
173 def test_raises_error_when_bookmark_is_specified_for_git(self):
174 with patch('rhodecode.model.db.Repository') as repo:
174 with patch('rhodecode.model.db.Repository') as repo:
175 repo.scm_instance().alias = 'git'
175 repo.scm_instance().alias = 'git'
176 repo.scm_instance().bookmarks = {
176 repo.scm_instance().bookmarks = {
177 self.bookmark_name: self.commit_hash}
177 self.bookmark_name: self.commit_hash}
178 with pytest.raises(ValueError):
178 with pytest.raises(ValueError):
179 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
179 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
180
180
181
181
182 class TestUserByNameOrError(object):
182 class TestUserByNameOrError(object):
183 def test_user_found_by_id(self):
183 def test_user_found_by_id(self):
184 fake_user = Mock(id=123)
184 fake_user = Mock(id=123)
185
185
186 patcher = patch('rhodecode.model.user.UserModel.get_user')
186 patcher = patch('rhodecode.model.user.UserModel.get_user')
187 with patcher as get_user:
187 with patcher as get_user:
188 get_user.return_value = fake_user
188 get_user.return_value = fake_user
189
189
190 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
190 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
191 with patcher as get_by_username:
191 with patcher as get_by_username:
192 result = utils.get_user_or_error(123)
192 result = utils.get_user_or_error(123)
193 assert result == fake_user
193 assert result == fake_user
194
194
195 def test_user_not_found_by_id_as_str(self):
195 def test_user_not_found_by_id_as_str(self):
196 fake_user = Mock(id=123)
196 fake_user = Mock(id=123)
197
197
198 patcher = patch('rhodecode.model.user.UserModel.get_user')
198 patcher = patch('rhodecode.model.user.UserModel.get_user')
199 with patcher as get_user:
199 with patcher as get_user:
200 get_user.return_value = fake_user
200 get_user.return_value = fake_user
201 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
201 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
202 with patcher as get_by_username:
202 with patcher as get_by_username:
203 get_by_username.return_value = None
203 get_by_username.return_value = None
204
204
205 with pytest.raises(JSONRPCError):
205 with pytest.raises(JSONRPCError):
206 utils.get_user_or_error('123')
206 utils.get_user_or_error('123')
207
207
208 def test_user_found_by_name(self):
208 def test_user_found_by_name(self):
209 fake_user = Mock(id=123)
209 fake_user = Mock(id=123)
210
210
211 patcher = patch('rhodecode.model.user.UserModel.get_user')
211 patcher = patch('rhodecode.model.user.UserModel.get_user')
212 with patcher as get_user:
212 with patcher as get_user:
213 get_user.return_value = None
213 get_user.return_value = None
214
214
215 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
215 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
216 with patcher as get_by_username:
216 with patcher as get_by_username:
217 get_by_username.return_value = fake_user
217 get_by_username.return_value = fake_user
218
218
219 result = utils.get_user_or_error('test')
219 result = utils.get_user_or_error('test')
220 assert result == fake_user
220 assert result == fake_user
221
221
222 def test_user_not_found_by_id(self):
222 def test_user_not_found_by_id(self):
223 patcher = patch('rhodecode.model.user.UserModel.get_user')
223 patcher = patch('rhodecode.model.user.UserModel.get_user')
224 with patcher as get_user:
224 with patcher as get_user:
225 get_user.return_value = None
225 get_user.return_value = None
226 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
226 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
227 with patcher as get_by_username:
227 with patcher as get_by_username:
228 get_by_username.return_value = None
228 get_by_username.return_value = None
229
229
230 with pytest.raises(JSONRPCError) as excinfo:
230 with pytest.raises(JSONRPCError) as excinfo:
231 utils.get_user_or_error(123)
231 utils.get_user_or_error(123)
232
232
233 expected_message = 'user `123` does not exist'
233 expected_message = 'user `123` does not exist'
234 assert excinfo.value.message == expected_message
234 assert excinfo.value.message == expected_message
235
235
236 def test_user_not_found_by_name(self):
236 def test_user_not_found_by_name(self):
237 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
237 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
238 with patcher as get_by_username:
238 with patcher as get_by_username:
239 get_by_username.return_value = None
239 get_by_username.return_value = None
240 with pytest.raises(JSONRPCError) as excinfo:
240 with pytest.raises(JSONRPCError) as excinfo:
241 utils.get_user_or_error('test')
241 utils.get_user_or_error('test')
242
242
243 expected_message = 'user `test` does not exist'
243 expected_message = 'user `test` does not exist'
244 assert excinfo.value.message == expected_message
244 assert excinfo.value.message == expected_message
245
245
246
246
247 class TestGetCommitDict(object):
247 class TestGetCommitDict(object):
248 @pytest.mark.parametrize('filename, expected', [
248 @pytest.mark.parametrize('filename, expected', [
249 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
249 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
250 (b'sp\xa4cial', u'sp\ufffdcial'),
250 (b'sp\xa4cial', u'sp\ufffdcial'),
251 ])
251 ])
252 def test_decodes_filenames_to_unicode(self, filename, expected):
252 def test_decodes_filenames_to_unicode(self, filename, expected):
253 result = utils._get_commit_dict(filename=filename, op='A')
253 result = utils._get_commit_dict(filename=filename, op='A')
254 assert result['filename'] == expected
254 assert result['filename'] == expected
255
255
256
256
257 class TestRepoAccess(object):
257 class TestRepoAccess(object):
258 def setup_method(self, method):
258 def setup_method(self, method):
259
259
260 self.admin_perm_patch = patch(
260 self.admin_perm_patch = patch(
261 'rhodecode.api.utils.HasPermissionAnyApi')
261 'rhodecode.api.utils.HasPermissionAnyApi')
262 self.repo_perm_patch = patch(
262 self.repo_perm_patch = patch(
263 'rhodecode.api.utils.HasRepoPermissionAnyApi')
263 'rhodecode.api.utils.HasRepoPermissionAnyApi')
264
264
265 def test_has_superadmin_permission_checks_for_admin(self):
265 def test_has_superadmin_permission_checks_for_admin(self):
266 admin_mock = Mock()
266 admin_mock = Mock()
267 with self.admin_perm_patch as amock:
267 with self.admin_perm_patch as amock:
268 amock.return_value = admin_mock
268 amock.return_value = admin_mock
269 assert utils.has_superadmin_permission('fake_user')
269 assert utils.has_superadmin_permission('fake_user')
270 amock.assert_called_once_with('hg.admin')
270 amock.assert_called_once_with('hg.admin')
271
271
272 admin_mock.assert_called_once_with(user='fake_user')
272 admin_mock.assert_called_once_with(user='fake_user')
273
273
274 def test_has_repo_permissions_checks_for_repo_access(self):
274 def test_has_repo_permissions_checks_for_repo_access(self):
275 repo_mock = Mock()
275 repo_mock = Mock()
276 fake_repo = Mock()
276 fake_repo = Mock()
277 with self.repo_perm_patch as rmock:
277 with self.repo_perm_patch as rmock:
278 rmock.return_value = repo_mock
278 rmock.return_value = repo_mock
279 assert utils.validate_repo_permissions(
279 assert utils.validate_repo_permissions(
280 'fake_user', 'fake_repo_id', fake_repo,
280 'fake_user', 'fake_repo_id', fake_repo,
281 ['perm1', 'perm2'])
281 ['perm1', 'perm2'])
282 rmock.assert_called_once_with(*['perm1', 'perm2'])
282 rmock.assert_called_once_with(*['perm1', 'perm2'])
283
283
284 repo_mock.assert_called_once_with(
284 repo_mock.assert_called_once_with(
285 user='fake_user', repo_name=fake_repo.repo_name)
285 user='fake_user', repo_name=fake_repo.repo_name)
286
286
287 def test_has_repo_permissions_raises_not_found(self):
287 def test_has_repo_permissions_raises_not_found(self):
288 repo_mock = Mock(return_value=False)
288 repo_mock = Mock(return_value=False)
289 fake_repo = Mock()
289 fake_repo = Mock()
290 with self.repo_perm_patch as rmock:
290 with self.repo_perm_patch as rmock:
291 rmock.return_value = repo_mock
291 rmock.return_value = repo_mock
292 with pytest.raises(JSONRPCError) as excinfo:
292 with pytest.raises(JSONRPCError) as excinfo:
293 utils.validate_repo_permissions(
293 utils.validate_repo_permissions(
294 'fake_user', 'fake_repo_id', fake_repo, 'perms')
294 'fake_user', 'fake_repo_id', fake_repo, 'perms')
295 assert 'fake_repo_id' in excinfo
295 assert 'fake_repo_id' in excinfo
@@ -1,122 +1,122 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import random
22 import random
23 import pytest
23 import pytest
24
24
25 from rhodecode.api.utils import get_origin
25 from rhodecode.api.utils import get_origin
26 from rhodecode.lib.ext_json import json
26 from rhodecode.lib.ext_json import json
27
27
28
28
29 def jsonify(obj):
29 def jsonify(obj):
30 return json.loads(json.dumps(obj))
30 return json.loads(json.dumps(obj))
31
31
32
32
33 API_URL = '/_admin/api'
33 API_URL = '/_admin/api'
34
34
35
35
36 def assert_call_ok(id_, given):
36 def assert_call_ok(id_, given):
37 expected = jsonify({
37 expected = jsonify({
38 'id': id_,
38 'id': id_,
39 'error': None,
39 'error': None,
40 'result': None
40 'result': None
41 })
41 })
42 given = json.loads(given)
42 given = json.loads(given)
43
43
44 assert expected['id'] == given['id']
44 assert expected['id'] == given['id']
45 assert expected['error'] == given['error']
45 assert expected['error'] == given['error']
46 return given['result']
46 return given['result']
47
47
48
48
49 def assert_ok(id_, expected, given):
49 def assert_ok(id_, expected, given):
50 given = json.loads(given)
50 given = json.loads(given)
51 if given.get('error'):
51 if given.get('error'):
52 pytest.fail("Unexpected ERROR in success response: {}".format(given['error']))
52 pytest.fail("Unexpected ERROR in success response: {}".format(given['error']))
53
53
54 expected = jsonify({
54 expected = jsonify({
55 'id': id_,
55 'id': id_,
56 'error': None,
56 'error': None,
57 'result': expected
57 'result': expected
58 })
58 })
59
59
60 assert expected == given
60 assert expected == given
61
61
62
62
63 def assert_error(id_, expected, given):
63 def assert_error(id_, expected, given):
64 expected = jsonify({
64 expected = jsonify({
65 'id': id_,
65 'id': id_,
66 'error': expected,
66 'error': expected,
67 'result': None
67 'result': None
68 })
68 })
69 given = json.loads(given)
69 given = json.loads(given)
70 assert expected == given
70 assert expected == given
71
71
72
72
73 def build_data(apikey, method, **kw):
73 def build_data(apikey, method, **kw):
74 """
74 """
75 Builds API data with given random ID
75 Builds API data with given random ID
76 """
76 """
77 random_id = random.randrange(1, 9999)
77 random_id = random.randrange(1, 9999)
78 return random_id, json.dumps({
78 return random_id, json.dumps({
79 "id": random_id,
79 "id": random_id,
80 "api_key": apikey,
80 "api_key": apikey,
81 "method": method,
81 "method": method,
82 "args": kw
82 "args": kw
83 })
83 })
84
84
85
85
86 def api_call(app, params, status=None):
86 def api_call(app, params, status=None):
87 response = app.post(
87 response = app.post(
88 API_URL, content_type='application/json', params=params, status=status)
88 API_URL, content_type='application/json', params=params, status=status)
89 return response
89 return response
90
90
91
91
92 def crash(*args, **kwargs):
92 def crash(*args, **kwargs):
93 raise Exception('Total Crash !')
93 raise Exception('Total Crash !')
94
94
95
95
96 def expected_permissions(object_with_permissions):
96 def expected_permissions(object_with_permissions):
97 """
97 """
98 Returns the expected permissions structure for the given object.
98 Returns the expected permissions structure for the given object.
99
99
100 The object is expected to be a `Repository`, `RepositoryGroup`,
100 The object is expected to be a `Repository`, `RepositoryGroup`,
101 or `UserGroup`. They all implement the same permission handling
101 or `UserGroup`. They all implement the same permission handling
102 API.
102 API.
103 """
103 """
104 permissions = []
104 permissions = []
105 for _user in object_with_permissions.permissions():
105 for _user in object_with_permissions.permissions():
106 user_data = {
106 user_data = {
107 'name': _user.username,
107 'name': _user.username,
108 'permission': _user.permission,
108 'permission': _user.permission,
109 'origin': get_origin(_user),
109 'origin': get_origin(_user),
110 'type': "user",
110 'type': "user",
111 }
111 }
112 permissions.append(user_data)
112 permissions.append(user_data)
113
113
114 for _user_group in object_with_permissions.permission_user_groups():
114 for _user_group in object_with_permissions.permission_user_groups():
115 user_group_data = {
115 user_group_data = {
116 'name': _user_group.users_group_name,
116 'name': _user_group.users_group_name,
117 'permission': _user_group.permission,
117 'permission': _user_group.permission,
118 'origin': get_origin(_user_group),
118 'origin': get_origin(_user_group),
119 'type': "user_group",
119 'type': "user_group",
120 }
120 }
121 permissions.append(user_group_data)
121 permissions.append(user_group_data)
122 return permissions
122 return permissions
@@ -1,453 +1,453 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2019 RhodeCode GmbH
3 # Copyright (C) 2014-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
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.lib.view_utils import get_commit_from_ref_name
33 from rhodecode.lib.view_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 msg = 'permission `{}` does not exist.'.format(permid)
311 msg = 'permission `{}` does not exist.'.format(permid)
312 if prefix:
312 if prefix:
313 msg += ' Permission should start with prefix: `{}`'.format(prefix)
313 msg += ' Permission should start with prefix: `{}`'.format(prefix)
314 raise JSONRPCError(msg)
314 raise JSONRPCError(msg)
315
315
316 if prefix:
316 if prefix:
317 if not perm.permission_name.startswith(prefix):
317 if not perm.permission_name.startswith(prefix):
318 raise JSONRPCError('permission `%s` is invalid, '
318 raise JSONRPCError('permission `%s` is invalid, '
319 'should start with %s' % (permid, prefix))
319 'should start with %s' % (permid, prefix))
320 return perm
320 return perm
321
321
322
322
323 def get_gist_or_error(gistid):
323 def get_gist_or_error(gistid):
324 """
324 """
325 Get gist by id or gist_access_id or return JsonRPCError if not found
325 Get gist by id or gist_access_id or return JsonRPCError if not found
326
326
327 :param gistid:
327 :param gistid:
328 """
328 """
329 from rhodecode.model.gist import GistModel
329 from rhodecode.model.gist import GistModel
330
330
331 gist = GistModel.cls.get_by_access_id(gistid)
331 gist = GistModel.cls.get_by_access_id(gistid)
332 if gist is None:
332 if gist is None:
333 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
333 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
334 return gist
334 return gist
335
335
336
336
337 def get_pull_request_or_error(pullrequestid):
337 def get_pull_request_or_error(pullrequestid):
338 """
338 """
339 Get pull request by id or return JsonRPCError if not found
339 Get pull request by id or return JsonRPCError if not found
340
340
341 :param pullrequestid:
341 :param pullrequestid:
342 """
342 """
343 from rhodecode.model.pull_request import PullRequestModel
343 from rhodecode.model.pull_request import PullRequestModel
344
344
345 try:
345 try:
346 pull_request = PullRequestModel().get(int(pullrequestid))
346 pull_request = PullRequestModel().get(int(pullrequestid))
347 except ValueError:
347 except ValueError:
348 raise JSONRPCError('pullrequestid must be an integer')
348 raise JSONRPCError('pullrequestid must be an integer')
349 if not pull_request:
349 if not pull_request:
350 raise JSONRPCError('pull request `%s` does not exist' % (
350 raise JSONRPCError('pull request `%s` does not exist' % (
351 pullrequestid,))
351 pullrequestid,))
352 return pull_request
352 return pull_request
353
353
354
354
355 def build_commit_data(commit, detail_level):
355 def build_commit_data(commit, detail_level):
356 parsed_diff = []
356 parsed_diff = []
357 if detail_level == 'extended':
357 if detail_level == 'extended':
358 for f_path in commit.added_paths:
358 for f_path in commit.added_paths:
359 parsed_diff.append(_get_commit_dict(filename=f_path, op='A'))
359 parsed_diff.append(_get_commit_dict(filename=f_path, op='A'))
360 for f_path in commit.changed_paths:
360 for f_path in commit.changed_paths:
361 parsed_diff.append(_get_commit_dict(filename=f_path, op='M'))
361 parsed_diff.append(_get_commit_dict(filename=f_path, op='M'))
362 for f_path in commit.removed_paths:
362 for f_path in commit.removed_paths:
363 parsed_diff.append(_get_commit_dict(filename=f_path, op='D'))
363 parsed_diff.append(_get_commit_dict(filename=f_path, op='D'))
364
364
365 elif detail_level == 'full':
365 elif detail_level == 'full':
366 from rhodecode.lib.diffs import DiffProcessor
366 from rhodecode.lib.diffs import DiffProcessor
367 diff_processor = DiffProcessor(commit.diff())
367 diff_processor = DiffProcessor(commit.diff())
368 for dp in diff_processor.prepare():
368 for dp in diff_processor.prepare():
369 del dp['stats']['ops']
369 del dp['stats']['ops']
370 _stats = dp['stats']
370 _stats = dp['stats']
371 parsed_diff.append(_get_commit_dict(
371 parsed_diff.append(_get_commit_dict(
372 filename=dp['filename'], op=dp['operation'],
372 filename=dp['filename'], op=dp['operation'],
373 new_revision=dp['new_revision'],
373 new_revision=dp['new_revision'],
374 old_revision=dp['old_revision'],
374 old_revision=dp['old_revision'],
375 raw_diff=dp['raw_diff'], stats=_stats))
375 raw_diff=dp['raw_diff'], stats=_stats))
376
376
377 return parsed_diff
377 return parsed_diff
378
378
379
379
380 def get_commit_or_error(ref, repo):
380 def get_commit_or_error(ref, repo):
381 try:
381 try:
382 ref_type, _, ref_hash = ref.split(':')
382 ref_type, _, ref_hash = ref.split(':')
383 except ValueError:
383 except ValueError:
384 raise JSONRPCError(
384 raise JSONRPCError(
385 'Ref `{ref}` given in a wrong format. Please check the API'
385 'Ref `{ref}` given in a wrong format. Please check the API'
386 ' documentation for more details'.format(ref=ref))
386 ' documentation for more details'.format(ref=ref))
387 try:
387 try:
388 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
388 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
389 # once get_commit supports ref_types
389 # once get_commit supports ref_types
390 return get_commit_from_ref_name(repo, ref_hash)
390 return get_commit_from_ref_name(repo, ref_hash)
391 except RepositoryError:
391 except RepositoryError:
392 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
392 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
393
393
394
394
395 def _get_ref_hash(repo, type_, name):
395 def _get_ref_hash(repo, type_, name):
396 vcs_repo = repo.scm_instance()
396 vcs_repo = repo.scm_instance()
397 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
397 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
398 return vcs_repo.branches[name]
398 return vcs_repo.branches[name]
399 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
399 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
400 return vcs_repo.bookmarks[name]
400 return vcs_repo.bookmarks[name]
401 else:
401 else:
402 raise ValueError()
402 raise ValueError()
403
403
404
404
405 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
405 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
406 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
406 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
407
407
408 def _parse_ref(type_, name, hash_=None):
408 def _parse_ref(type_, name, hash_=None):
409 return type_, name, hash_
409 return type_, name, hash_
410
410
411 try:
411 try:
412 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
412 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
413 except TypeError:
413 except TypeError:
414 raise JSONRPCError(
414 raise JSONRPCError(
415 'Ref `{ref}` given in a wrong format. Please check the API'
415 'Ref `{ref}` given in a wrong format. Please check the API'
416 ' documentation for more details'.format(ref=ref))
416 ' documentation for more details'.format(ref=ref))
417
417
418 if ref_type not in allowed_ref_types:
418 if ref_type not in allowed_ref_types:
419 raise JSONRPCError(
419 raise JSONRPCError(
420 'Ref `{ref}` type is not allowed. '
420 'Ref `{ref}` type is not allowed. '
421 'Only:{allowed_refs} are possible.'.format(
421 'Only:{allowed_refs} are possible.'.format(
422 ref=ref, allowed_refs=allowed_ref_types))
422 ref=ref, allowed_refs=allowed_ref_types))
423
423
424 try:
424 try:
425 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
425 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
426 except (KeyError, ValueError):
426 except (KeyError, ValueError):
427 raise JSONRPCError(
427 raise JSONRPCError(
428 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
428 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
429 type=ref_type, name=ref_name))
429 type=ref_type, name=ref_name))
430
430
431 return ':'.join([ref_type, ref_name, ref_hash])
431 return ':'.join([ref_type, ref_name, ref_hash])
432
432
433
433
434 def _get_commit_dict(
434 def _get_commit_dict(
435 filename, op, new_revision=None, old_revision=None,
435 filename, op, new_revision=None, old_revision=None,
436 raw_diff=None, stats=None):
436 raw_diff=None, stats=None):
437 if stats is None:
437 if stats is None:
438 stats = {
438 stats = {
439 "added": None,
439 "added": None,
440 "binary": None,
440 "binary": None,
441 "deleted": None
441 "deleted": None
442 }
442 }
443 return {
443 return {
444 "filename": safe_unicode(filename),
444 "filename": safe_unicode(filename),
445 "op": op,
445 "op": op,
446
446
447 # extra details
447 # extra details
448 "new_revision": new_revision,
448 "new_revision": new_revision,
449 "old_revision": old_revision,
449 "old_revision": old_revision,
450
450
451 "raw_diff": raw_diff,
451 "raw_diff": raw_diff,
452 "stats": stats
452 "stats": stats
453 }
453 }
@@ -1,19 +1,19 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2015-2019 RhodeCode GmbH
3 # Copyright (C) 2015-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,102 +1,102 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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:: javascript
69 .. code-block:: javascript
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-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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,1018 +1,1018 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
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, validate_set_owner_permissions)
29 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
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, PullRequest
35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment, PullRequest
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 merge_state=Optional(False)):
47 merge_state=Optional(False)):
48 """
48 """
49 Get a pull request based on the given ID.
49 Get a pull request based on the given ID.
50
50
51 :param apiuser: This is filled automatically from the |authtoken|.
51 :param apiuser: This is filled automatically from the |authtoken|.
52 :type apiuser: AuthUser
52 :type apiuser: AuthUser
53 :param repoid: Optional, repository name or repository ID from where
53 :param repoid: Optional, repository name or repository ID from where
54 the pull request was opened.
54 the pull request was opened.
55 :type repoid: str or int
55 :type repoid: str or int
56 :param pullrequestid: ID of the requested pull request.
56 :param pullrequestid: ID of the requested pull request.
57 :type pullrequestid: int
57 :type pullrequestid: int
58 :param merge_state: Optional calculate merge state for each repository.
58 :param merge_state: Optional calculate merge state for each repository.
59 This could result in longer time to fetch the data
59 This could result in longer time to fetch the data
60 :type merge_state: bool
60 :type merge_state: bool
61
61
62 Example output:
62 Example output:
63
63
64 .. code-block:: bash
64 .. code-block:: bash
65
65
66 "id": <id_given_in_input>,
66 "id": <id_given_in_input>,
67 "result":
67 "result":
68 {
68 {
69 "pull_request_id": "<pull_request_id>",
69 "pull_request_id": "<pull_request_id>",
70 "url": "<url>",
70 "url": "<url>",
71 "title": "<title>",
71 "title": "<title>",
72 "description": "<description>",
72 "description": "<description>",
73 "status" : "<status>",
73 "status" : "<status>",
74 "created_on": "<date_time_created>",
74 "created_on": "<date_time_created>",
75 "updated_on": "<date_time_updated>",
75 "updated_on": "<date_time_updated>",
76 "versions": "<number_or_versions_of_pr>",
76 "versions": "<number_or_versions_of_pr>",
77 "commit_ids": [
77 "commit_ids": [
78 ...
78 ...
79 "<commit_id>",
79 "<commit_id>",
80 "<commit_id>",
80 "<commit_id>",
81 ...
81 ...
82 ],
82 ],
83 "review_status": "<review_status>",
83 "review_status": "<review_status>",
84 "mergeable": {
84 "mergeable": {
85 "status": "<bool>",
85 "status": "<bool>",
86 "message": "<message>",
86 "message": "<message>",
87 },
87 },
88 "source": {
88 "source": {
89 "clone_url": "<clone_url>",
89 "clone_url": "<clone_url>",
90 "repository": "<repository_name>",
90 "repository": "<repository_name>",
91 "reference":
91 "reference":
92 {
92 {
93 "name": "<name>",
93 "name": "<name>",
94 "type": "<type>",
94 "type": "<type>",
95 "commit_id": "<commit_id>",
95 "commit_id": "<commit_id>",
96 }
96 }
97 },
97 },
98 "target": {
98 "target": {
99 "clone_url": "<clone_url>",
99 "clone_url": "<clone_url>",
100 "repository": "<repository_name>",
100 "repository": "<repository_name>",
101 "reference":
101 "reference":
102 {
102 {
103 "name": "<name>",
103 "name": "<name>",
104 "type": "<type>",
104 "type": "<type>",
105 "commit_id": "<commit_id>",
105 "commit_id": "<commit_id>",
106 }
106 }
107 },
107 },
108 "merge": {
108 "merge": {
109 "clone_url": "<clone_url>",
109 "clone_url": "<clone_url>",
110 "reference":
110 "reference":
111 {
111 {
112 "name": "<name>",
112 "name": "<name>",
113 "type": "<type>",
113 "type": "<type>",
114 "commit_id": "<commit_id>",
114 "commit_id": "<commit_id>",
115 }
115 }
116 },
116 },
117 "author": <user_obj>,
117 "author": <user_obj>,
118 "reviewers": [
118 "reviewers": [
119 ...
119 ...
120 {
120 {
121 "user": "<user_obj>",
121 "user": "<user_obj>",
122 "review_status": "<review_status>",
122 "review_status": "<review_status>",
123 }
123 }
124 ...
124 ...
125 ]
125 ]
126 },
126 },
127 "error": null
127 "error": null
128 """
128 """
129
129
130 pull_request = get_pull_request_or_error(pullrequestid)
130 pull_request = get_pull_request_or_error(pullrequestid)
131 if Optional.extract(repoid):
131 if Optional.extract(repoid):
132 repo = get_repo_or_error(repoid)
132 repo = get_repo_or_error(repoid)
133 else:
133 else:
134 repo = pull_request.target_repo
134 repo = pull_request.target_repo
135
135
136 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
136 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
137 raise JSONRPCError('repository `%s` or pull request `%s` '
137 raise JSONRPCError('repository `%s` or pull request `%s` '
138 'does not exist' % (repoid, pullrequestid))
138 'does not exist' % (repoid, pullrequestid))
139
139
140 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
140 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
141 # otherwise we can lock the repo on calculation of merge state while update/merge
141 # otherwise we can lock the repo on calculation of merge state while update/merge
142 # is happening.
142 # is happening.
143 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
143 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
144 merge_state = Optional.extract(merge_state, binary=True) and pr_created
144 merge_state = Optional.extract(merge_state, binary=True) and pr_created
145 data = pull_request.get_api_data(with_merge_state=merge_state)
145 data = pull_request.get_api_data(with_merge_state=merge_state)
146 return data
146 return data
147
147
148
148
149 @jsonrpc_method()
149 @jsonrpc_method()
150 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
150 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
151 merge_state=Optional(False)):
151 merge_state=Optional(False)):
152 """
152 """
153 Get all pull requests from the repository specified in `repoid`.
153 Get all pull requests from the repository specified in `repoid`.
154
154
155 :param apiuser: This is filled automatically from the |authtoken|.
155 :param apiuser: This is filled automatically from the |authtoken|.
156 :type apiuser: AuthUser
156 :type apiuser: AuthUser
157 :param repoid: Optional repository name or repository ID.
157 :param repoid: Optional repository name or repository ID.
158 :type repoid: str or int
158 :type repoid: str or int
159 :param status: Only return pull requests with the specified status.
159 :param status: Only return pull requests with the specified status.
160 Valid options are.
160 Valid options are.
161 * ``new`` (default)
161 * ``new`` (default)
162 * ``open``
162 * ``open``
163 * ``closed``
163 * ``closed``
164 :type status: str
164 :type status: str
165 :param merge_state: Optional calculate merge state for each repository.
165 :param merge_state: Optional calculate merge state for each repository.
166 This could result in longer time to fetch the data
166 This could result in longer time to fetch the data
167 :type merge_state: bool
167 :type merge_state: bool
168
168
169 Example output:
169 Example output:
170
170
171 .. code-block:: bash
171 .. code-block:: bash
172
172
173 "id": <id_given_in_input>,
173 "id": <id_given_in_input>,
174 "result":
174 "result":
175 [
175 [
176 ...
176 ...
177 {
177 {
178 "pull_request_id": "<pull_request_id>",
178 "pull_request_id": "<pull_request_id>",
179 "url": "<url>",
179 "url": "<url>",
180 "title" : "<title>",
180 "title" : "<title>",
181 "description": "<description>",
181 "description": "<description>",
182 "status": "<status>",
182 "status": "<status>",
183 "created_on": "<date_time_created>",
183 "created_on": "<date_time_created>",
184 "updated_on": "<date_time_updated>",
184 "updated_on": "<date_time_updated>",
185 "commit_ids": [
185 "commit_ids": [
186 ...
186 ...
187 "<commit_id>",
187 "<commit_id>",
188 "<commit_id>",
188 "<commit_id>",
189 ...
189 ...
190 ],
190 ],
191 "review_status": "<review_status>",
191 "review_status": "<review_status>",
192 "mergeable": {
192 "mergeable": {
193 "status": "<bool>",
193 "status": "<bool>",
194 "message: "<message>",
194 "message: "<message>",
195 },
195 },
196 "source": {
196 "source": {
197 "clone_url": "<clone_url>",
197 "clone_url": "<clone_url>",
198 "reference":
198 "reference":
199 {
199 {
200 "name": "<name>",
200 "name": "<name>",
201 "type": "<type>",
201 "type": "<type>",
202 "commit_id": "<commit_id>",
202 "commit_id": "<commit_id>",
203 }
203 }
204 },
204 },
205 "target": {
205 "target": {
206 "clone_url": "<clone_url>",
206 "clone_url": "<clone_url>",
207 "reference":
207 "reference":
208 {
208 {
209 "name": "<name>",
209 "name": "<name>",
210 "type": "<type>",
210 "type": "<type>",
211 "commit_id": "<commit_id>",
211 "commit_id": "<commit_id>",
212 }
212 }
213 },
213 },
214 "merge": {
214 "merge": {
215 "clone_url": "<clone_url>",
215 "clone_url": "<clone_url>",
216 "reference":
216 "reference":
217 {
217 {
218 "name": "<name>",
218 "name": "<name>",
219 "type": "<type>",
219 "type": "<type>",
220 "commit_id": "<commit_id>",
220 "commit_id": "<commit_id>",
221 }
221 }
222 },
222 },
223 "author": <user_obj>,
223 "author": <user_obj>,
224 "reviewers": [
224 "reviewers": [
225 ...
225 ...
226 {
226 {
227 "user": "<user_obj>",
227 "user": "<user_obj>",
228 "review_status": "<review_status>",
228 "review_status": "<review_status>",
229 }
229 }
230 ...
230 ...
231 ]
231 ]
232 }
232 }
233 ...
233 ...
234 ],
234 ],
235 "error": null
235 "error": null
236
236
237 """
237 """
238 repo = get_repo_or_error(repoid)
238 repo = get_repo_or_error(repoid)
239 if not has_superadmin_permission(apiuser):
239 if not has_superadmin_permission(apiuser):
240 _perms = (
240 _perms = (
241 'repository.admin', 'repository.write', 'repository.read',)
241 'repository.admin', 'repository.write', 'repository.read',)
242 validate_repo_permissions(apiuser, repoid, repo, _perms)
242 validate_repo_permissions(apiuser, repoid, repo, _perms)
243
243
244 status = Optional.extract(status)
244 status = Optional.extract(status)
245 merge_state = Optional.extract(merge_state, binary=True)
245 merge_state = Optional.extract(merge_state, binary=True)
246 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
246 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
247 order_by='id', order_dir='desc')
247 order_by='id', order_dir='desc')
248 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
248 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
249 return data
249 return data
250
250
251
251
252 @jsonrpc_method()
252 @jsonrpc_method()
253 def merge_pull_request(
253 def merge_pull_request(
254 request, apiuser, pullrequestid, repoid=Optional(None),
254 request, apiuser, pullrequestid, repoid=Optional(None),
255 userid=Optional(OAttr('apiuser'))):
255 userid=Optional(OAttr('apiuser'))):
256 """
256 """
257 Merge the pull request specified by `pullrequestid` into its target
257 Merge the pull request specified by `pullrequestid` into its target
258 repository.
258 repository.
259
259
260 :param apiuser: This is filled automatically from the |authtoken|.
260 :param apiuser: This is filled automatically from the |authtoken|.
261 :type apiuser: AuthUser
261 :type apiuser: AuthUser
262 :param repoid: Optional, repository name or repository ID of the
262 :param repoid: Optional, repository name or repository ID of the
263 target repository to which the |pr| is to be merged.
263 target repository to which the |pr| is to be merged.
264 :type repoid: str or int
264 :type repoid: str or int
265 :param pullrequestid: ID of the pull request which shall be merged.
265 :param pullrequestid: ID of the pull request which shall be merged.
266 :type pullrequestid: int
266 :type pullrequestid: int
267 :param userid: Merge the pull request as this user.
267 :param userid: Merge the pull request as this user.
268 :type userid: Optional(str or int)
268 :type userid: Optional(str or int)
269
269
270 Example output:
270 Example output:
271
271
272 .. code-block:: bash
272 .. code-block:: bash
273
273
274 "id": <id_given_in_input>,
274 "id": <id_given_in_input>,
275 "result": {
275 "result": {
276 "executed": "<bool>",
276 "executed": "<bool>",
277 "failure_reason": "<int>",
277 "failure_reason": "<int>",
278 "merge_status_message": "<str>",
278 "merge_status_message": "<str>",
279 "merge_commit_id": "<merge_commit_id>",
279 "merge_commit_id": "<merge_commit_id>",
280 "possible": "<bool>",
280 "possible": "<bool>",
281 "merge_ref": {
281 "merge_ref": {
282 "commit_id": "<commit_id>",
282 "commit_id": "<commit_id>",
283 "type": "<type>",
283 "type": "<type>",
284 "name": "<name>"
284 "name": "<name>"
285 }
285 }
286 },
286 },
287 "error": null
287 "error": null
288 """
288 """
289 pull_request = get_pull_request_or_error(pullrequestid)
289 pull_request = get_pull_request_or_error(pullrequestid)
290 if Optional.extract(repoid):
290 if Optional.extract(repoid):
291 repo = get_repo_or_error(repoid)
291 repo = get_repo_or_error(repoid)
292 else:
292 else:
293 repo = pull_request.target_repo
293 repo = pull_request.target_repo
294 auth_user = apiuser
294 auth_user = apiuser
295 if not isinstance(userid, Optional):
295 if not isinstance(userid, Optional):
296 if (has_superadmin_permission(apiuser) or
296 if (has_superadmin_permission(apiuser) or
297 HasRepoPermissionAnyApi('repository.admin')(
297 HasRepoPermissionAnyApi('repository.admin')(
298 user=apiuser, repo_name=repo.repo_name)):
298 user=apiuser, repo_name=repo.repo_name)):
299 apiuser = get_user_or_error(userid)
299 apiuser = get_user_or_error(userid)
300 auth_user = apiuser.AuthUser()
300 auth_user = apiuser.AuthUser()
301 else:
301 else:
302 raise JSONRPCError('userid is not the same as your user')
302 raise JSONRPCError('userid is not the same as your user')
303
303
304 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
304 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
305 raise JSONRPCError(
305 raise JSONRPCError(
306 'Operation forbidden because pull request is in state {}, '
306 'Operation forbidden because pull request is in state {}, '
307 'only state {} is allowed.'.format(
307 'only state {} is allowed.'.format(
308 pull_request.pull_request_state, PullRequest.STATE_CREATED))
308 pull_request.pull_request_state, PullRequest.STATE_CREATED))
309
309
310 with pull_request.set_state(PullRequest.STATE_UPDATING):
310 with pull_request.set_state(PullRequest.STATE_UPDATING):
311 check = MergeCheck.validate(pull_request, auth_user=auth_user,
311 check = MergeCheck.validate(pull_request, auth_user=auth_user,
312 translator=request.translate)
312 translator=request.translate)
313 merge_possible = not check.failed
313 merge_possible = not check.failed
314
314
315 if not merge_possible:
315 if not merge_possible:
316 error_messages = []
316 error_messages = []
317 for err_type, error_msg in check.errors:
317 for err_type, error_msg in check.errors:
318 error_msg = request.translate(error_msg)
318 error_msg = request.translate(error_msg)
319 error_messages.append(error_msg)
319 error_messages.append(error_msg)
320
320
321 reasons = ','.join(error_messages)
321 reasons = ','.join(error_messages)
322 raise JSONRPCError(
322 raise JSONRPCError(
323 'merge not possible for following reasons: {}'.format(reasons))
323 'merge not possible for following reasons: {}'.format(reasons))
324
324
325 target_repo = pull_request.target_repo
325 target_repo = pull_request.target_repo
326 extras = vcs_operation_context(
326 extras = vcs_operation_context(
327 request.environ, repo_name=target_repo.repo_name,
327 request.environ, repo_name=target_repo.repo_name,
328 username=auth_user.username, action='push',
328 username=auth_user.username, action='push',
329 scm=target_repo.repo_type)
329 scm=target_repo.repo_type)
330 with pull_request.set_state(PullRequest.STATE_UPDATING):
330 with pull_request.set_state(PullRequest.STATE_UPDATING):
331 merge_response = PullRequestModel().merge_repo(
331 merge_response = PullRequestModel().merge_repo(
332 pull_request, apiuser, extras=extras)
332 pull_request, apiuser, extras=extras)
333 if merge_response.executed:
333 if merge_response.executed:
334 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
334 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
335
335
336 Session().commit()
336 Session().commit()
337
337
338 # In previous versions the merge response directly contained the merge
338 # In previous versions the merge response directly contained the merge
339 # commit id. It is now contained in the merge reference object. To be
339 # commit id. It is now contained in the merge reference object. To be
340 # backwards compatible we have to extract it again.
340 # backwards compatible we have to extract it again.
341 merge_response = merge_response.asdict()
341 merge_response = merge_response.asdict()
342 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
342 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
343
343
344 return merge_response
344 return merge_response
345
345
346
346
347 @jsonrpc_method()
347 @jsonrpc_method()
348 def get_pull_request_comments(
348 def get_pull_request_comments(
349 request, apiuser, pullrequestid, repoid=Optional(None)):
349 request, apiuser, pullrequestid, repoid=Optional(None)):
350 """
350 """
351 Get all comments of pull request specified with the `pullrequestid`
351 Get all comments of pull request specified with the `pullrequestid`
352
352
353 :param apiuser: This is filled automatically from the |authtoken|.
353 :param apiuser: This is filled automatically from the |authtoken|.
354 :type apiuser: AuthUser
354 :type apiuser: AuthUser
355 :param repoid: Optional repository name or repository ID.
355 :param repoid: Optional repository name or repository ID.
356 :type repoid: str or int
356 :type repoid: str or int
357 :param pullrequestid: The pull request ID.
357 :param pullrequestid: The pull request ID.
358 :type pullrequestid: int
358 :type pullrequestid: int
359
359
360 Example output:
360 Example output:
361
361
362 .. code-block:: bash
362 .. code-block:: bash
363
363
364 id : <id_given_in_input>
364 id : <id_given_in_input>
365 result : [
365 result : [
366 {
366 {
367 "comment_author": {
367 "comment_author": {
368 "active": true,
368 "active": true,
369 "full_name_or_username": "Tom Gore",
369 "full_name_or_username": "Tom Gore",
370 "username": "admin"
370 "username": "admin"
371 },
371 },
372 "comment_created_on": "2017-01-02T18:43:45.533",
372 "comment_created_on": "2017-01-02T18:43:45.533",
373 "comment_f_path": null,
373 "comment_f_path": null,
374 "comment_id": 25,
374 "comment_id": 25,
375 "comment_lineno": null,
375 "comment_lineno": null,
376 "comment_status": {
376 "comment_status": {
377 "status": "under_review",
377 "status": "under_review",
378 "status_lbl": "Under Review"
378 "status_lbl": "Under Review"
379 },
379 },
380 "comment_text": "Example text",
380 "comment_text": "Example text",
381 "comment_type": null,
381 "comment_type": null,
382 "pull_request_version": null,
382 "pull_request_version": null,
383 "comment_commit_id": None,
383 "comment_commit_id": None,
384 "comment_pull_request_id": <pull_request_id>
384 "comment_pull_request_id": <pull_request_id>
385 }
385 }
386 ],
386 ],
387 error : null
387 error : null
388 """
388 """
389
389
390 pull_request = get_pull_request_or_error(pullrequestid)
390 pull_request = get_pull_request_or_error(pullrequestid)
391 if Optional.extract(repoid):
391 if Optional.extract(repoid):
392 repo = get_repo_or_error(repoid)
392 repo = get_repo_or_error(repoid)
393 else:
393 else:
394 repo = pull_request.target_repo
394 repo = pull_request.target_repo
395
395
396 if not PullRequestModel().check_user_read(
396 if not PullRequestModel().check_user_read(
397 pull_request, apiuser, api=True):
397 pull_request, apiuser, api=True):
398 raise JSONRPCError('repository `%s` or pull request `%s` '
398 raise JSONRPCError('repository `%s` or pull request `%s` '
399 'does not exist' % (repoid, pullrequestid))
399 'does not exist' % (repoid, pullrequestid))
400
400
401 (pull_request_latest,
401 (pull_request_latest,
402 pull_request_at_ver,
402 pull_request_at_ver,
403 pull_request_display_obj,
403 pull_request_display_obj,
404 at_version) = PullRequestModel().get_pr_version(
404 at_version) = PullRequestModel().get_pr_version(
405 pull_request.pull_request_id, version=None)
405 pull_request.pull_request_id, version=None)
406
406
407 versions = pull_request_display_obj.versions()
407 versions = pull_request_display_obj.versions()
408 ver_map = {
408 ver_map = {
409 ver.pull_request_version_id: cnt
409 ver.pull_request_version_id: cnt
410 for cnt, ver in enumerate(versions, 1)
410 for cnt, ver in enumerate(versions, 1)
411 }
411 }
412
412
413 # GENERAL COMMENTS with versions #
413 # GENERAL COMMENTS with versions #
414 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
414 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
415 q = q.order_by(ChangesetComment.comment_id.asc())
415 q = q.order_by(ChangesetComment.comment_id.asc())
416 general_comments = q.all()
416 general_comments = q.all()
417
417
418 # INLINE COMMENTS with versions #
418 # INLINE COMMENTS with versions #
419 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
419 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
420 q = q.order_by(ChangesetComment.comment_id.asc())
420 q = q.order_by(ChangesetComment.comment_id.asc())
421 inline_comments = q.all()
421 inline_comments = q.all()
422
422
423 data = []
423 data = []
424 for comment in inline_comments + general_comments:
424 for comment in inline_comments + general_comments:
425 full_data = comment.get_api_data()
425 full_data = comment.get_api_data()
426 pr_version_id = None
426 pr_version_id = None
427 if comment.pull_request_version_id:
427 if comment.pull_request_version_id:
428 pr_version_id = 'v{}'.format(
428 pr_version_id = 'v{}'.format(
429 ver_map[comment.pull_request_version_id])
429 ver_map[comment.pull_request_version_id])
430
430
431 # sanitize some entries
431 # sanitize some entries
432
432
433 full_data['pull_request_version'] = pr_version_id
433 full_data['pull_request_version'] = pr_version_id
434 full_data['comment_author'] = {
434 full_data['comment_author'] = {
435 'username': full_data['comment_author'].username,
435 'username': full_data['comment_author'].username,
436 'full_name_or_username': full_data['comment_author'].full_name_or_username,
436 'full_name_or_username': full_data['comment_author'].full_name_or_username,
437 'active': full_data['comment_author'].active,
437 'active': full_data['comment_author'].active,
438 }
438 }
439
439
440 if full_data['comment_status']:
440 if full_data['comment_status']:
441 full_data['comment_status'] = {
441 full_data['comment_status'] = {
442 'status': full_data['comment_status'][0].status,
442 'status': full_data['comment_status'][0].status,
443 'status_lbl': full_data['comment_status'][0].status_lbl,
443 'status_lbl': full_data['comment_status'][0].status_lbl,
444 }
444 }
445 else:
445 else:
446 full_data['comment_status'] = {}
446 full_data['comment_status'] = {}
447
447
448 data.append(full_data)
448 data.append(full_data)
449 return data
449 return data
450
450
451
451
452 @jsonrpc_method()
452 @jsonrpc_method()
453 def comment_pull_request(
453 def comment_pull_request(
454 request, apiuser, pullrequestid, repoid=Optional(None),
454 request, apiuser, pullrequestid, repoid=Optional(None),
455 message=Optional(None), commit_id=Optional(None), status=Optional(None),
455 message=Optional(None), commit_id=Optional(None), status=Optional(None),
456 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
456 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
457 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
457 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
458 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
458 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
459 """
459 """
460 Comment on the pull request specified with the `pullrequestid`,
460 Comment on the pull request specified with the `pullrequestid`,
461 in the |repo| specified by the `repoid`, and optionally change the
461 in the |repo| specified by the `repoid`, and optionally change the
462 review status.
462 review status.
463
463
464 :param apiuser: This is filled automatically from the |authtoken|.
464 :param apiuser: This is filled automatically from the |authtoken|.
465 :type apiuser: AuthUser
465 :type apiuser: AuthUser
466 :param repoid: Optional repository name or repository ID.
466 :param repoid: Optional repository name or repository ID.
467 :type repoid: str or int
467 :type repoid: str or int
468 :param pullrequestid: The pull request ID.
468 :param pullrequestid: The pull request ID.
469 :type pullrequestid: int
469 :type pullrequestid: int
470 :param commit_id: Specify the commit_id for which to set a comment. If
470 :param commit_id: Specify the commit_id for which to set a comment. If
471 given commit_id is different than latest in the PR status
471 given commit_id is different than latest in the PR status
472 change won't be performed.
472 change won't be performed.
473 :type commit_id: str
473 :type commit_id: str
474 :param message: The text content of the comment.
474 :param message: The text content of the comment.
475 :type message: str
475 :type message: str
476 :param status: (**Optional**) Set the approval status of the pull
476 :param status: (**Optional**) Set the approval status of the pull
477 request. One of: 'not_reviewed', 'approved', 'rejected',
477 request. One of: 'not_reviewed', 'approved', 'rejected',
478 'under_review'
478 'under_review'
479 :type status: str
479 :type status: str
480 :param comment_type: Comment type, one of: 'note', 'todo'
480 :param comment_type: Comment type, one of: 'note', 'todo'
481 :type comment_type: Optional(str), default: 'note'
481 :type comment_type: Optional(str), default: 'note'
482 :param resolves_comment_id: id of comment which this one will resolve
482 :param resolves_comment_id: id of comment which this one will resolve
483 :type resolves_comment_id: Optional(int)
483 :type resolves_comment_id: Optional(int)
484 :param extra_recipients: list of user ids or usernames to add
484 :param extra_recipients: list of user ids or usernames to add
485 notifications for this comment. Acts like a CC for notification
485 notifications for this comment. Acts like a CC for notification
486 :type extra_recipients: Optional(list)
486 :type extra_recipients: Optional(list)
487 :param userid: Comment on the pull request as this user
487 :param userid: Comment on the pull request as this user
488 :type userid: Optional(str or int)
488 :type userid: Optional(str or int)
489 :param send_email: Define if this comment should also send email notification
489 :param send_email: Define if this comment should also send email notification
490 :type send_email: Optional(bool)
490 :type send_email: Optional(bool)
491
491
492 Example output:
492 Example output:
493
493
494 .. code-block:: bash
494 .. code-block:: bash
495
495
496 id : <id_given_in_input>
496 id : <id_given_in_input>
497 result : {
497 result : {
498 "pull_request_id": "<Integer>",
498 "pull_request_id": "<Integer>",
499 "comment_id": "<Integer>",
499 "comment_id": "<Integer>",
500 "status": {"given": <given_status>,
500 "status": {"given": <given_status>,
501 "was_changed": <bool status_was_actually_changed> },
501 "was_changed": <bool status_was_actually_changed> },
502 },
502 },
503 error : null
503 error : null
504 """
504 """
505 pull_request = get_pull_request_or_error(pullrequestid)
505 pull_request = get_pull_request_or_error(pullrequestid)
506 if Optional.extract(repoid):
506 if Optional.extract(repoid):
507 repo = get_repo_or_error(repoid)
507 repo = get_repo_or_error(repoid)
508 else:
508 else:
509 repo = pull_request.target_repo
509 repo = pull_request.target_repo
510
510
511 auth_user = apiuser
511 auth_user = apiuser
512 if not isinstance(userid, Optional):
512 if not isinstance(userid, Optional):
513 if (has_superadmin_permission(apiuser) or
513 if (has_superadmin_permission(apiuser) or
514 HasRepoPermissionAnyApi('repository.admin')(
514 HasRepoPermissionAnyApi('repository.admin')(
515 user=apiuser, repo_name=repo.repo_name)):
515 user=apiuser, repo_name=repo.repo_name)):
516 apiuser = get_user_or_error(userid)
516 apiuser = get_user_or_error(userid)
517 auth_user = apiuser.AuthUser()
517 auth_user = apiuser.AuthUser()
518 else:
518 else:
519 raise JSONRPCError('userid is not the same as your user')
519 raise JSONRPCError('userid is not the same as your user')
520
520
521 if pull_request.is_closed():
521 if pull_request.is_closed():
522 raise JSONRPCError(
522 raise JSONRPCError(
523 'pull request `%s` comment failed, pull request is closed' % (
523 'pull request `%s` comment failed, pull request is closed' % (
524 pullrequestid,))
524 pullrequestid,))
525
525
526 if not PullRequestModel().check_user_read(
526 if not PullRequestModel().check_user_read(
527 pull_request, apiuser, api=True):
527 pull_request, apiuser, api=True):
528 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
528 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
529 message = Optional.extract(message)
529 message = Optional.extract(message)
530 status = Optional.extract(status)
530 status = Optional.extract(status)
531 commit_id = Optional.extract(commit_id)
531 commit_id = Optional.extract(commit_id)
532 comment_type = Optional.extract(comment_type)
532 comment_type = Optional.extract(comment_type)
533 resolves_comment_id = Optional.extract(resolves_comment_id)
533 resolves_comment_id = Optional.extract(resolves_comment_id)
534 extra_recipients = Optional.extract(extra_recipients)
534 extra_recipients = Optional.extract(extra_recipients)
535 send_email = Optional.extract(send_email, binary=True)
535 send_email = Optional.extract(send_email, binary=True)
536
536
537 if not message and not status:
537 if not message and not status:
538 raise JSONRPCError(
538 raise JSONRPCError(
539 'Both message and status parameters are missing. '
539 'Both message and status parameters are missing. '
540 'At least one is required.')
540 'At least one is required.')
541
541
542 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
542 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
543 status is not None):
543 status is not None):
544 raise JSONRPCError('Unknown comment status: `%s`' % status)
544 raise JSONRPCError('Unknown comment status: `%s`' % status)
545
545
546 if commit_id and commit_id not in pull_request.revisions:
546 if commit_id and commit_id not in pull_request.revisions:
547 raise JSONRPCError(
547 raise JSONRPCError(
548 'Invalid commit_id `%s` for this pull request.' % commit_id)
548 'Invalid commit_id `%s` for this pull request.' % commit_id)
549
549
550 allowed_to_change_status = PullRequestModel().check_user_change_status(
550 allowed_to_change_status = PullRequestModel().check_user_change_status(
551 pull_request, apiuser)
551 pull_request, apiuser)
552
552
553 # if commit_id is passed re-validated if user is allowed to change status
553 # if commit_id is passed re-validated if user is allowed to change status
554 # based on latest commit_id from the PR
554 # based on latest commit_id from the PR
555 if commit_id:
555 if commit_id:
556 commit_idx = pull_request.revisions.index(commit_id)
556 commit_idx = pull_request.revisions.index(commit_id)
557 if commit_idx != 0:
557 if commit_idx != 0:
558 allowed_to_change_status = False
558 allowed_to_change_status = False
559
559
560 if resolves_comment_id:
560 if resolves_comment_id:
561 comment = ChangesetComment.get(resolves_comment_id)
561 comment = ChangesetComment.get(resolves_comment_id)
562 if not comment:
562 if not comment:
563 raise JSONRPCError(
563 raise JSONRPCError(
564 'Invalid resolves_comment_id `%s` for this pull request.'
564 'Invalid resolves_comment_id `%s` for this pull request.'
565 % resolves_comment_id)
565 % resolves_comment_id)
566 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
566 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
567 raise JSONRPCError(
567 raise JSONRPCError(
568 'Comment `%s` is wrong type for setting status to resolved.'
568 'Comment `%s` is wrong type for setting status to resolved.'
569 % resolves_comment_id)
569 % resolves_comment_id)
570
570
571 text = message
571 text = message
572 status_label = ChangesetStatus.get_status_lbl(status)
572 status_label = ChangesetStatus.get_status_lbl(status)
573 if status and allowed_to_change_status:
573 if status and allowed_to_change_status:
574 st_message = ('Status change %(transition_icon)s %(status)s'
574 st_message = ('Status change %(transition_icon)s %(status)s'
575 % {'transition_icon': '>', 'status': status_label})
575 % {'transition_icon': '>', 'status': status_label})
576 text = message or st_message
576 text = message or st_message
577
577
578 rc_config = SettingsModel().get_all_settings()
578 rc_config = SettingsModel().get_all_settings()
579 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
579 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
580
580
581 status_change = status and allowed_to_change_status
581 status_change = status and allowed_to_change_status
582 comment = CommentsModel().create(
582 comment = CommentsModel().create(
583 text=text,
583 text=text,
584 repo=pull_request.target_repo.repo_id,
584 repo=pull_request.target_repo.repo_id,
585 user=apiuser.user_id,
585 user=apiuser.user_id,
586 pull_request=pull_request.pull_request_id,
586 pull_request=pull_request.pull_request_id,
587 f_path=None,
587 f_path=None,
588 line_no=None,
588 line_no=None,
589 status_change=(status_label if status_change else None),
589 status_change=(status_label if status_change else None),
590 status_change_type=(status if status_change else None),
590 status_change_type=(status if status_change else None),
591 closing_pr=False,
591 closing_pr=False,
592 renderer=renderer,
592 renderer=renderer,
593 comment_type=comment_type,
593 comment_type=comment_type,
594 resolves_comment_id=resolves_comment_id,
594 resolves_comment_id=resolves_comment_id,
595 auth_user=auth_user,
595 auth_user=auth_user,
596 extra_recipients=extra_recipients,
596 extra_recipients=extra_recipients,
597 send_email=send_email
597 send_email=send_email
598 )
598 )
599
599
600 if allowed_to_change_status and status:
600 if allowed_to_change_status and status:
601 old_calculated_status = pull_request.calculated_review_status()
601 old_calculated_status = pull_request.calculated_review_status()
602 ChangesetStatusModel().set_status(
602 ChangesetStatusModel().set_status(
603 pull_request.target_repo.repo_id,
603 pull_request.target_repo.repo_id,
604 status,
604 status,
605 apiuser.user_id,
605 apiuser.user_id,
606 comment,
606 comment,
607 pull_request=pull_request.pull_request_id
607 pull_request=pull_request.pull_request_id
608 )
608 )
609 Session().flush()
609 Session().flush()
610
610
611 Session().commit()
611 Session().commit()
612
612
613 PullRequestModel().trigger_pull_request_hook(
613 PullRequestModel().trigger_pull_request_hook(
614 pull_request, apiuser, 'comment',
614 pull_request, apiuser, 'comment',
615 data={'comment': comment})
615 data={'comment': comment})
616
616
617 if allowed_to_change_status and status:
617 if allowed_to_change_status and status:
618 # we now calculate the status of pull request, and based on that
618 # we now calculate the status of pull request, and based on that
619 # calculation we set the commits status
619 # calculation we set the commits status
620 calculated_status = pull_request.calculated_review_status()
620 calculated_status = pull_request.calculated_review_status()
621 if old_calculated_status != calculated_status:
621 if old_calculated_status != calculated_status:
622 PullRequestModel().trigger_pull_request_hook(
622 PullRequestModel().trigger_pull_request_hook(
623 pull_request, apiuser, 'review_status_change',
623 pull_request, apiuser, 'review_status_change',
624 data={'status': calculated_status})
624 data={'status': calculated_status})
625
625
626 data = {
626 data = {
627 'pull_request_id': pull_request.pull_request_id,
627 'pull_request_id': pull_request.pull_request_id,
628 'comment_id': comment.comment_id if comment else None,
628 'comment_id': comment.comment_id if comment else None,
629 'status': {'given': status, 'was_changed': status_change},
629 'status': {'given': status, 'was_changed': status_change},
630 }
630 }
631 return data
631 return data
632
632
633
633
634 @jsonrpc_method()
634 @jsonrpc_method()
635 def create_pull_request(
635 def create_pull_request(
636 request, apiuser, source_repo, target_repo, source_ref, target_ref,
636 request, apiuser, source_repo, target_repo, source_ref, target_ref,
637 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
637 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
638 description_renderer=Optional(''), reviewers=Optional(None)):
638 description_renderer=Optional(''), reviewers=Optional(None)):
639 """
639 """
640 Creates a new pull request.
640 Creates a new pull request.
641
641
642 Accepts refs in the following formats:
642 Accepts refs in the following formats:
643
643
644 * branch:<branch_name>:<sha>
644 * branch:<branch_name>:<sha>
645 * branch:<branch_name>
645 * branch:<branch_name>
646 * bookmark:<bookmark_name>:<sha> (Mercurial only)
646 * bookmark:<bookmark_name>:<sha> (Mercurial only)
647 * bookmark:<bookmark_name> (Mercurial only)
647 * bookmark:<bookmark_name> (Mercurial only)
648
648
649 :param apiuser: This is filled automatically from the |authtoken|.
649 :param apiuser: This is filled automatically from the |authtoken|.
650 :type apiuser: AuthUser
650 :type apiuser: AuthUser
651 :param source_repo: Set the source repository name.
651 :param source_repo: Set the source repository name.
652 :type source_repo: str
652 :type source_repo: str
653 :param target_repo: Set the target repository name.
653 :param target_repo: Set the target repository name.
654 :type target_repo: str
654 :type target_repo: str
655 :param source_ref: Set the source ref name.
655 :param source_ref: Set the source ref name.
656 :type source_ref: str
656 :type source_ref: str
657 :param target_ref: Set the target ref name.
657 :param target_ref: Set the target ref name.
658 :type target_ref: str
658 :type target_ref: str
659 :param owner: user_id or username
659 :param owner: user_id or username
660 :type owner: Optional(str)
660 :type owner: Optional(str)
661 :param title: Optionally Set the pull request title, it's generated otherwise
661 :param title: Optionally Set the pull request title, it's generated otherwise
662 :type title: str
662 :type title: str
663 :param description: Set the pull request description.
663 :param description: Set the pull request description.
664 :type description: Optional(str)
664 :type description: Optional(str)
665 :type description_renderer: Optional(str)
665 :type description_renderer: Optional(str)
666 :param description_renderer: Set pull request renderer for the description.
666 :param description_renderer: Set pull request renderer for the description.
667 It should be 'rst', 'markdown' or 'plain'. If not give default
667 It should be 'rst', 'markdown' or 'plain'. If not give default
668 system renderer will be used
668 system renderer will be used
669 :param reviewers: Set the new pull request reviewers list.
669 :param reviewers: Set the new pull request reviewers list.
670 Reviewer defined by review rules will be added automatically to the
670 Reviewer defined by review rules will be added automatically to the
671 defined list.
671 defined list.
672 :type reviewers: Optional(list)
672 :type reviewers: Optional(list)
673 Accepts username strings or objects of the format:
673 Accepts username strings or objects of the format:
674
674
675 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
675 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
676 """
676 """
677
677
678 source_db_repo = get_repo_or_error(source_repo)
678 source_db_repo = get_repo_or_error(source_repo)
679 target_db_repo = get_repo_or_error(target_repo)
679 target_db_repo = get_repo_or_error(target_repo)
680 if not has_superadmin_permission(apiuser):
680 if not has_superadmin_permission(apiuser):
681 _perms = ('repository.admin', 'repository.write', 'repository.read',)
681 _perms = ('repository.admin', 'repository.write', 'repository.read',)
682 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
682 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
683
683
684 owner = validate_set_owner_permissions(apiuser, owner)
684 owner = validate_set_owner_permissions(apiuser, owner)
685
685
686 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
686 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
687 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
687 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
688
688
689 source_scm = source_db_repo.scm_instance()
689 source_scm = source_db_repo.scm_instance()
690 target_scm = target_db_repo.scm_instance()
690 target_scm = target_db_repo.scm_instance()
691
691
692 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
692 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
693 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
693 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
694
694
695 ancestor = source_scm.get_common_ancestor(
695 ancestor = source_scm.get_common_ancestor(
696 source_commit.raw_id, target_commit.raw_id, target_scm)
696 source_commit.raw_id, target_commit.raw_id, target_scm)
697 if not ancestor:
697 if not ancestor:
698 raise JSONRPCError('no common ancestor found')
698 raise JSONRPCError('no common ancestor found')
699
699
700 # recalculate target ref based on ancestor
700 # recalculate target ref based on ancestor
701 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
701 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
702 full_target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
702 full_target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
703
703
704 commit_ranges = target_scm.compare(
704 commit_ranges = target_scm.compare(
705 target_commit.raw_id, source_commit.raw_id, source_scm,
705 target_commit.raw_id, source_commit.raw_id, source_scm,
706 merge=True, pre_load=[])
706 merge=True, pre_load=[])
707
707
708 if not commit_ranges:
708 if not commit_ranges:
709 raise JSONRPCError('no commits found')
709 raise JSONRPCError('no commits found')
710
710
711 reviewer_objects = Optional.extract(reviewers) or []
711 reviewer_objects = Optional.extract(reviewers) or []
712
712
713 # serialize and validate passed in given reviewers
713 # serialize and validate passed in given reviewers
714 if reviewer_objects:
714 if reviewer_objects:
715 schema = ReviewerListSchema()
715 schema = ReviewerListSchema()
716 try:
716 try:
717 reviewer_objects = schema.deserialize(reviewer_objects)
717 reviewer_objects = schema.deserialize(reviewer_objects)
718 except Invalid as err:
718 except Invalid as err:
719 raise JSONRPCValidationError(colander_exc=err)
719 raise JSONRPCValidationError(colander_exc=err)
720
720
721 # validate users
721 # validate users
722 for reviewer_object in reviewer_objects:
722 for reviewer_object in reviewer_objects:
723 user = get_user_or_error(reviewer_object['username'])
723 user = get_user_or_error(reviewer_object['username'])
724 reviewer_object['user_id'] = user.user_id
724 reviewer_object['user_id'] = user.user_id
725
725
726 get_default_reviewers_data, validate_default_reviewers = \
726 get_default_reviewers_data, validate_default_reviewers = \
727 PullRequestModel().get_reviewer_functions()
727 PullRequestModel().get_reviewer_functions()
728
728
729 # recalculate reviewers logic, to make sure we can validate this
729 # recalculate reviewers logic, to make sure we can validate this
730 reviewer_rules = get_default_reviewers_data(
730 reviewer_rules = get_default_reviewers_data(
731 owner, source_db_repo,
731 owner, source_db_repo,
732 source_commit, target_db_repo, target_commit)
732 source_commit, target_db_repo, target_commit)
733
733
734 # now MERGE our given with the calculated
734 # now MERGE our given with the calculated
735 reviewer_objects = reviewer_rules['reviewers'] + reviewer_objects
735 reviewer_objects = reviewer_rules['reviewers'] + reviewer_objects
736
736
737 try:
737 try:
738 reviewers = validate_default_reviewers(
738 reviewers = validate_default_reviewers(
739 reviewer_objects, reviewer_rules)
739 reviewer_objects, reviewer_rules)
740 except ValueError as e:
740 except ValueError as e:
741 raise JSONRPCError('Reviewers Validation: {}'.format(e))
741 raise JSONRPCError('Reviewers Validation: {}'.format(e))
742
742
743 title = Optional.extract(title)
743 title = Optional.extract(title)
744 if not title:
744 if not title:
745 title_source_ref = source_ref.split(':', 2)[1]
745 title_source_ref = source_ref.split(':', 2)[1]
746 title = PullRequestModel().generate_pullrequest_title(
746 title = PullRequestModel().generate_pullrequest_title(
747 source=source_repo,
747 source=source_repo,
748 source_ref=title_source_ref,
748 source_ref=title_source_ref,
749 target=target_repo
749 target=target_repo
750 )
750 )
751 # fetch renderer, if set fallback to plain in case of PR
751 # fetch renderer, if set fallback to plain in case of PR
752 rc_config = SettingsModel().get_all_settings()
752 rc_config = SettingsModel().get_all_settings()
753 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
753 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
754 description = Optional.extract(description)
754 description = Optional.extract(description)
755 description_renderer = Optional.extract(description_renderer) or default_system_renderer
755 description_renderer = Optional.extract(description_renderer) or default_system_renderer
756
756
757 pull_request = PullRequestModel().create(
757 pull_request = PullRequestModel().create(
758 created_by=owner.user_id,
758 created_by=owner.user_id,
759 source_repo=source_repo,
759 source_repo=source_repo,
760 source_ref=full_source_ref,
760 source_ref=full_source_ref,
761 target_repo=target_repo,
761 target_repo=target_repo,
762 target_ref=full_target_ref,
762 target_ref=full_target_ref,
763 revisions=[commit.raw_id for commit in reversed(commit_ranges)],
763 revisions=[commit.raw_id for commit in reversed(commit_ranges)],
764 reviewers=reviewers,
764 reviewers=reviewers,
765 title=title,
765 title=title,
766 description=description,
766 description=description,
767 description_renderer=description_renderer,
767 description_renderer=description_renderer,
768 reviewer_data=reviewer_rules,
768 reviewer_data=reviewer_rules,
769 auth_user=apiuser
769 auth_user=apiuser
770 )
770 )
771
771
772 Session().commit()
772 Session().commit()
773 data = {
773 data = {
774 'msg': 'Created new pull request `{}`'.format(title),
774 'msg': 'Created new pull request `{}`'.format(title),
775 'pull_request_id': pull_request.pull_request_id,
775 'pull_request_id': pull_request.pull_request_id,
776 }
776 }
777 return data
777 return data
778
778
779
779
780 @jsonrpc_method()
780 @jsonrpc_method()
781 def update_pull_request(
781 def update_pull_request(
782 request, apiuser, pullrequestid, repoid=Optional(None),
782 request, apiuser, pullrequestid, repoid=Optional(None),
783 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
783 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
784 reviewers=Optional(None), update_commits=Optional(None)):
784 reviewers=Optional(None), update_commits=Optional(None)):
785 """
785 """
786 Updates a pull request.
786 Updates a pull request.
787
787
788 :param apiuser: This is filled automatically from the |authtoken|.
788 :param apiuser: This is filled automatically from the |authtoken|.
789 :type apiuser: AuthUser
789 :type apiuser: AuthUser
790 :param repoid: Optional repository name or repository ID.
790 :param repoid: Optional repository name or repository ID.
791 :type repoid: str or int
791 :type repoid: str or int
792 :param pullrequestid: The pull request ID.
792 :param pullrequestid: The pull request ID.
793 :type pullrequestid: int
793 :type pullrequestid: int
794 :param title: Set the pull request title.
794 :param title: Set the pull request title.
795 :type title: str
795 :type title: str
796 :param description: Update pull request description.
796 :param description: Update pull request description.
797 :type description: Optional(str)
797 :type description: Optional(str)
798 :type description_renderer: Optional(str)
798 :type description_renderer: Optional(str)
799 :param description_renderer: Update pull request renderer for the description.
799 :param description_renderer: Update pull request renderer for the description.
800 It should be 'rst', 'markdown' or 'plain'
800 It should be 'rst', 'markdown' or 'plain'
801 :param reviewers: Update pull request reviewers list with new value.
801 :param reviewers: Update pull request reviewers list with new value.
802 :type reviewers: Optional(list)
802 :type reviewers: Optional(list)
803 Accepts username strings or objects of the format:
803 Accepts username strings or objects of the format:
804
804
805 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
805 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
806
806
807 :param update_commits: Trigger update of commits for this pull request
807 :param update_commits: Trigger update of commits for this pull request
808 :type: update_commits: Optional(bool)
808 :type: update_commits: Optional(bool)
809
809
810 Example output:
810 Example output:
811
811
812 .. code-block:: bash
812 .. code-block:: bash
813
813
814 id : <id_given_in_input>
814 id : <id_given_in_input>
815 result : {
815 result : {
816 "msg": "Updated pull request `63`",
816 "msg": "Updated pull request `63`",
817 "pull_request": <pull_request_object>,
817 "pull_request": <pull_request_object>,
818 "updated_reviewers": {
818 "updated_reviewers": {
819 "added": [
819 "added": [
820 "username"
820 "username"
821 ],
821 ],
822 "removed": []
822 "removed": []
823 },
823 },
824 "updated_commits": {
824 "updated_commits": {
825 "added": [
825 "added": [
826 "<sha1_hash>"
826 "<sha1_hash>"
827 ],
827 ],
828 "common": [
828 "common": [
829 "<sha1_hash>",
829 "<sha1_hash>",
830 "<sha1_hash>",
830 "<sha1_hash>",
831 ],
831 ],
832 "removed": []
832 "removed": []
833 }
833 }
834 }
834 }
835 error : null
835 error : null
836 """
836 """
837
837
838 pull_request = get_pull_request_or_error(pullrequestid)
838 pull_request = get_pull_request_or_error(pullrequestid)
839 if Optional.extract(repoid):
839 if Optional.extract(repoid):
840 repo = get_repo_or_error(repoid)
840 repo = get_repo_or_error(repoid)
841 else:
841 else:
842 repo = pull_request.target_repo
842 repo = pull_request.target_repo
843
843
844 if not PullRequestModel().check_user_update(
844 if not PullRequestModel().check_user_update(
845 pull_request, apiuser, api=True):
845 pull_request, apiuser, api=True):
846 raise JSONRPCError(
846 raise JSONRPCError(
847 'pull request `%s` update failed, no permission to update.' % (
847 'pull request `%s` update failed, no permission to update.' % (
848 pullrequestid,))
848 pullrequestid,))
849 if pull_request.is_closed():
849 if pull_request.is_closed():
850 raise JSONRPCError(
850 raise JSONRPCError(
851 'pull request `%s` update failed, pull request is closed' % (
851 'pull request `%s` update failed, pull request is closed' % (
852 pullrequestid,))
852 pullrequestid,))
853
853
854 reviewer_objects = Optional.extract(reviewers) or []
854 reviewer_objects = Optional.extract(reviewers) or []
855
855
856 if reviewer_objects:
856 if reviewer_objects:
857 schema = ReviewerListSchema()
857 schema = ReviewerListSchema()
858 try:
858 try:
859 reviewer_objects = schema.deserialize(reviewer_objects)
859 reviewer_objects = schema.deserialize(reviewer_objects)
860 except Invalid as err:
860 except Invalid as err:
861 raise JSONRPCValidationError(colander_exc=err)
861 raise JSONRPCValidationError(colander_exc=err)
862
862
863 # validate users
863 # validate users
864 for reviewer_object in reviewer_objects:
864 for reviewer_object in reviewer_objects:
865 user = get_user_or_error(reviewer_object['username'])
865 user = get_user_or_error(reviewer_object['username'])
866 reviewer_object['user_id'] = user.user_id
866 reviewer_object['user_id'] = user.user_id
867
867
868 get_default_reviewers_data, get_validated_reviewers = \
868 get_default_reviewers_data, get_validated_reviewers = \
869 PullRequestModel().get_reviewer_functions()
869 PullRequestModel().get_reviewer_functions()
870
870
871 # re-use stored rules
871 # re-use stored rules
872 reviewer_rules = pull_request.reviewer_data
872 reviewer_rules = pull_request.reviewer_data
873 try:
873 try:
874 reviewers = get_validated_reviewers(
874 reviewers = get_validated_reviewers(
875 reviewer_objects, reviewer_rules)
875 reviewer_objects, reviewer_rules)
876 except ValueError as e:
876 except ValueError as e:
877 raise JSONRPCError('Reviewers Validation: {}'.format(e))
877 raise JSONRPCError('Reviewers Validation: {}'.format(e))
878 else:
878 else:
879 reviewers = []
879 reviewers = []
880
880
881 title = Optional.extract(title)
881 title = Optional.extract(title)
882 description = Optional.extract(description)
882 description = Optional.extract(description)
883 description_renderer = Optional.extract(description_renderer)
883 description_renderer = Optional.extract(description_renderer)
884
884
885 if title or description:
885 if title or description:
886 PullRequestModel().edit(
886 PullRequestModel().edit(
887 pull_request,
887 pull_request,
888 title or pull_request.title,
888 title or pull_request.title,
889 description or pull_request.description,
889 description or pull_request.description,
890 description_renderer or pull_request.description_renderer,
890 description_renderer or pull_request.description_renderer,
891 apiuser)
891 apiuser)
892 Session().commit()
892 Session().commit()
893
893
894 commit_changes = {"added": [], "common": [], "removed": []}
894 commit_changes = {"added": [], "common": [], "removed": []}
895 if str2bool(Optional.extract(update_commits)):
895 if str2bool(Optional.extract(update_commits)):
896
896
897 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
897 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
898 raise JSONRPCError(
898 raise JSONRPCError(
899 'Operation forbidden because pull request is in state {}, '
899 'Operation forbidden because pull request is in state {}, '
900 'only state {} is allowed.'.format(
900 'only state {} is allowed.'.format(
901 pull_request.pull_request_state, PullRequest.STATE_CREATED))
901 pull_request.pull_request_state, PullRequest.STATE_CREATED))
902
902
903 with pull_request.set_state(PullRequest.STATE_UPDATING):
903 with pull_request.set_state(PullRequest.STATE_UPDATING):
904 if PullRequestModel().has_valid_update_type(pull_request):
904 if PullRequestModel().has_valid_update_type(pull_request):
905 db_user = apiuser.get_instance()
905 db_user = apiuser.get_instance()
906 update_response = PullRequestModel().update_commits(
906 update_response = PullRequestModel().update_commits(
907 pull_request, db_user)
907 pull_request, db_user)
908 commit_changes = update_response.changes or commit_changes
908 commit_changes = update_response.changes or commit_changes
909 Session().commit()
909 Session().commit()
910
910
911 reviewers_changes = {"added": [], "removed": []}
911 reviewers_changes = {"added": [], "removed": []}
912 if reviewers:
912 if reviewers:
913 old_calculated_status = pull_request.calculated_review_status()
913 old_calculated_status = pull_request.calculated_review_status()
914 added_reviewers, removed_reviewers = \
914 added_reviewers, removed_reviewers = \
915 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
915 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
916
916
917 reviewers_changes['added'] = sorted(
917 reviewers_changes['added'] = sorted(
918 [get_user_or_error(n).username for n in added_reviewers])
918 [get_user_or_error(n).username for n in added_reviewers])
919 reviewers_changes['removed'] = sorted(
919 reviewers_changes['removed'] = sorted(
920 [get_user_or_error(n).username for n in removed_reviewers])
920 [get_user_or_error(n).username for n in removed_reviewers])
921 Session().commit()
921 Session().commit()
922
922
923 # trigger status changed if change in reviewers changes the status
923 # trigger status changed if change in reviewers changes the status
924 calculated_status = pull_request.calculated_review_status()
924 calculated_status = pull_request.calculated_review_status()
925 if old_calculated_status != calculated_status:
925 if old_calculated_status != calculated_status:
926 PullRequestModel().trigger_pull_request_hook(
926 PullRequestModel().trigger_pull_request_hook(
927 pull_request, apiuser, 'review_status_change',
927 pull_request, apiuser, 'review_status_change',
928 data={'status': calculated_status})
928 data={'status': calculated_status})
929
929
930 data = {
930 data = {
931 'msg': 'Updated pull request `{}`'.format(
931 'msg': 'Updated pull request `{}`'.format(
932 pull_request.pull_request_id),
932 pull_request.pull_request_id),
933 'pull_request': pull_request.get_api_data(),
933 'pull_request': pull_request.get_api_data(),
934 'updated_commits': commit_changes,
934 'updated_commits': commit_changes,
935 'updated_reviewers': reviewers_changes
935 'updated_reviewers': reviewers_changes
936 }
936 }
937
937
938 return data
938 return data
939
939
940
940
941 @jsonrpc_method()
941 @jsonrpc_method()
942 def close_pull_request(
942 def close_pull_request(
943 request, apiuser, pullrequestid, repoid=Optional(None),
943 request, apiuser, pullrequestid, repoid=Optional(None),
944 userid=Optional(OAttr('apiuser')), message=Optional('')):
944 userid=Optional(OAttr('apiuser')), message=Optional('')):
945 """
945 """
946 Close the pull request specified by `pullrequestid`.
946 Close the pull request specified by `pullrequestid`.
947
947
948 :param apiuser: This is filled automatically from the |authtoken|.
948 :param apiuser: This is filled automatically from the |authtoken|.
949 :type apiuser: AuthUser
949 :type apiuser: AuthUser
950 :param repoid: Repository name or repository ID to which the pull
950 :param repoid: Repository name or repository ID to which the pull
951 request belongs.
951 request belongs.
952 :type repoid: str or int
952 :type repoid: str or int
953 :param pullrequestid: ID of the pull request to be closed.
953 :param pullrequestid: ID of the pull request to be closed.
954 :type pullrequestid: int
954 :type pullrequestid: int
955 :param userid: Close the pull request as this user.
955 :param userid: Close the pull request as this user.
956 :type userid: Optional(str or int)
956 :type userid: Optional(str or int)
957 :param message: Optional message to close the Pull Request with. If not
957 :param message: Optional message to close the Pull Request with. If not
958 specified it will be generated automatically.
958 specified it will be generated automatically.
959 :type message: Optional(str)
959 :type message: Optional(str)
960
960
961 Example output:
961 Example output:
962
962
963 .. code-block:: bash
963 .. code-block:: bash
964
964
965 "id": <id_given_in_input>,
965 "id": <id_given_in_input>,
966 "result": {
966 "result": {
967 "pull_request_id": "<int>",
967 "pull_request_id": "<int>",
968 "close_status": "<str:status_lbl>,
968 "close_status": "<str:status_lbl>,
969 "closed": "<bool>"
969 "closed": "<bool>"
970 },
970 },
971 "error": null
971 "error": null
972
972
973 """
973 """
974 _ = request.translate
974 _ = request.translate
975
975
976 pull_request = get_pull_request_or_error(pullrequestid)
976 pull_request = get_pull_request_or_error(pullrequestid)
977 if Optional.extract(repoid):
977 if Optional.extract(repoid):
978 repo = get_repo_or_error(repoid)
978 repo = get_repo_or_error(repoid)
979 else:
979 else:
980 repo = pull_request.target_repo
980 repo = pull_request.target_repo
981
981
982 if not isinstance(userid, Optional):
982 if not isinstance(userid, Optional):
983 if (has_superadmin_permission(apiuser) or
983 if (has_superadmin_permission(apiuser) or
984 HasRepoPermissionAnyApi('repository.admin')(
984 HasRepoPermissionAnyApi('repository.admin')(
985 user=apiuser, repo_name=repo.repo_name)):
985 user=apiuser, repo_name=repo.repo_name)):
986 apiuser = get_user_or_error(userid)
986 apiuser = get_user_or_error(userid)
987 else:
987 else:
988 raise JSONRPCError('userid is not the same as your user')
988 raise JSONRPCError('userid is not the same as your user')
989
989
990 if pull_request.is_closed():
990 if pull_request.is_closed():
991 raise JSONRPCError(
991 raise JSONRPCError(
992 'pull request `%s` is already closed' % (pullrequestid,))
992 'pull request `%s` is already closed' % (pullrequestid,))
993
993
994 # only owner or admin or person with write permissions
994 # only owner or admin or person with write permissions
995 allowed_to_close = PullRequestModel().check_user_update(
995 allowed_to_close = PullRequestModel().check_user_update(
996 pull_request, apiuser, api=True)
996 pull_request, apiuser, api=True)
997
997
998 if not allowed_to_close:
998 if not allowed_to_close:
999 raise JSONRPCError(
999 raise JSONRPCError(
1000 'pull request `%s` close failed, no permission to close.' % (
1000 'pull request `%s` close failed, no permission to close.' % (
1001 pullrequestid,))
1001 pullrequestid,))
1002
1002
1003 # message we're using to close the PR, else it's automatically generated
1003 # message we're using to close the PR, else it's automatically generated
1004 message = Optional.extract(message)
1004 message = Optional.extract(message)
1005
1005
1006 # finally close the PR, with proper message comment
1006 # finally close the PR, with proper message comment
1007 comment, status = PullRequestModel().close_pull_request_with_comment(
1007 comment, status = PullRequestModel().close_pull_request_with_comment(
1008 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1008 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1009 status_lbl = ChangesetStatus.get_status_lbl(status)
1009 status_lbl = ChangesetStatus.get_status_lbl(status)
1010
1010
1011 Session().commit()
1011 Session().commit()
1012
1012
1013 data = {
1013 data = {
1014 'pull_request_id': pull_request.pull_request_id,
1014 'pull_request_id': pull_request.pull_request_id,
1015 'close_status': status_lbl,
1015 'close_status': status_lbl,
1016 'closed': True,
1016 'closed': True,
1017 }
1017 }
1018 return data
1018 return data
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
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