##// END OF EJS Templates
copyrights: updated for 2023
super-admin -
r5088:8f6d1ed6 default
parent child Browse files
Show More

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

@@ -1,58 +1,58 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest # noqa
21 21
22 22 # keep the imports to have a toplevel conftest.py but still importable from EE edition
23 23 from rhodecode.tests.conftest_common import ( # noqa
24 24 pytest_generate_tests,
25 25 pytest_runtest_makereport,
26 26 pytest_addoption
27 27 )
28 28
29 29
30 30 pytest_plugins = [
31 31 "rhodecode.tests.fixture_mods.fixture_pyramid",
32 32 "rhodecode.tests.fixture_mods.fixture_utils",
33 33 ]
34 34
35 35
36 36 def pytest_configure(config):
37 37 from rhodecode.config import patches # noqa
38 38
39 39
40 40 def pytest_collection_modifyitems(session, config, items):
41 41 # nottest marked, compare nose, used for transition from nose to pytest
42 42 remaining = [
43 43 i for i in items if getattr(i.obj, '__test__', True)]
44 44 items[:] = remaining
45 45
46 46 # NOTE(marcink): custom test ordering, db tests and vcstests are slowest and should
47 47 # be executed at the end for faster test feedback
48 48 def sorter(item):
49 49 pos = 0
50 50 key = item._nodeid
51 51 if key.startswith('rhodecode/tests/database'):
52 52 pos = 1
53 53 elif key.startswith('rhodecode/tests/vcs_operations'):
54 54 pos = 2
55 55
56 56 return pos
57 57
58 58 items.sort(key=sorter)
@@ -1,88 +1,88 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import datetime
22 22 import collections
23 23
24 24 now = datetime.datetime.now()
25 25 now = now.strftime("%Y-%m-%d %H:%M:%S") + '.' + "{:03d}".format(int(now.microsecond/1000))
26 26
27 27 print(f'{now} Starting RhodeCode imports...')
28 28
29 29 VERSION = tuple(open(os.path.join(
30 30 os.path.dirname(__file__), 'VERSION')).read().split('.'))
31 31
32 32 BACKENDS = collections.OrderedDict()
33 33
34 34 BACKENDS['hg'] = 'Mercurial repository'
35 35 BACKENDS['git'] = 'Git repository'
36 36 BACKENDS['svn'] = 'Subversion repository'
37 37
38 38
39 39 CELERY_ENABLED = False
40 40 CELERY_EAGER = False
41 41
42 42 # link to config for pyramid
43 43 CONFIG = {}
44 44
45 45
46 46 class ConfigGet:
47 47 NotGiven = object()
48 48
49 49 def _get_val_or_missing(self, key, missing):
50 50 if key not in CONFIG:
51 51 if missing == self.NotGiven:
52 52 return missing
53 53 # we don't get key, we don't get missing value, return nothing similar as config.get(key)
54 54 return None
55 55 else:
56 56 val = CONFIG[key]
57 57 return val
58 58
59 59 def get_str(self, key, missing=NotGiven):
60 60 from rhodecode.lib.str_utils import safe_str
61 61 val = self._get_val_or_missing(key, missing)
62 62 return safe_str(val)
63 63
64 64 def get_int(self, key, missing=NotGiven):
65 65 from rhodecode.lib.str_utils import safe_int
66 66 val = self._get_val_or_missing(key, missing)
67 67 return safe_int(val)
68 68
69 69 def get_bool(self, key, missing=NotGiven):
70 70 from rhodecode.lib.type_utils import str2bool
71 71 val = self._get_val_or_missing(key, missing)
72 72 return str2bool(val)
73 73
74 74 # Populated with the settings dictionary from application init in
75 75 # rhodecode.conf.environment.load_pyramid_environment
76 76 PYRAMID_SETTINGS = {}
77 77
78 78 # Linked module for extensions
79 79 EXTENSIONS = {}
80 80
81 81 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
82 82 __dbversion__ = 114 # defines current db version for migrations
83 83 __license__ = 'AGPLv3, and Commercial License'
84 84 __author__ = 'RhodeCode GmbH'
85 85 __url__ = 'https://code.rhodecode.com'
86 86
87 87 is_test = False
88 88 disable_error_handler = False
@@ -1,576 +1,576 b''
1 1
2 2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import itertools
22 22 import logging
23 23 import sys
24 24 import fnmatch
25 25
26 26 import decorator
27 27 import typing
28 28 import venusian
29 29 from collections import OrderedDict
30 30
31 31 from pyramid.exceptions import ConfigurationError
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34 from pyramid.httpexceptions import HTTPNotFound
35 35
36 36 from rhodecode.api.exc import (
37 37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 38 from rhodecode.apps._base import TemplateArgs
39 39 from rhodecode.lib.auth import AuthUser
40 40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
41 41 from rhodecode.lib.exc_tracking import store_exception
42 42 from rhodecode.lib import ext_json
43 43 from rhodecode.lib.utils2 import safe_str
44 44 from rhodecode.lib.plugins.utils import get_plugin_settings
45 45 from rhodecode.model.db import User, UserApiKeys
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49 DEFAULT_RENDERER = 'jsonrpc_renderer'
50 50 DEFAULT_URL = '/_admin/apiv2'
51 51
52 52
53 53 def find_methods(jsonrpc_methods, pattern):
54 54 matches = OrderedDict()
55 55 if not isinstance(pattern, (list, tuple)):
56 56 pattern = [pattern]
57 57
58 58 for single_pattern in pattern:
59 59 for method_name, method in jsonrpc_methods.items():
60 60 if fnmatch.fnmatch(method_name, single_pattern):
61 61 matches[method_name] = method
62 62 return matches
63 63
64 64
65 65 class ExtJsonRenderer(object):
66 66 """
67 67 Custom renderer that makes use of our ext_json lib
68 68
69 69 """
70 70
71 71 def __init__(self):
72 72 self.serializer = ext_json.formatted_json
73 73
74 74 def __call__(self, info):
75 75 """ Returns a plain JSON-encoded string with content-type
76 76 ``application/json``. The content-type may be overridden by
77 77 setting ``request.response.content_type``."""
78 78
79 79 def _render(value, system):
80 80 request = system.get('request')
81 81 if request is not None:
82 82 response = request.response
83 83 ct = response.content_type
84 84 if ct == response.default_content_type:
85 85 response.content_type = 'application/json'
86 86
87 87 return self.serializer(value)
88 88
89 89 return _render
90 90
91 91
92 92 def jsonrpc_response(request, result):
93 93 rpc_id = getattr(request, 'rpc_id', None)
94 94
95 95 ret_value = ''
96 96 if rpc_id:
97 97 ret_value = {'id': rpc_id, 'result': result, 'error': None}
98 98
99 99 # fetch deprecation warnings, and store it inside results
100 100 deprecation = getattr(request, 'rpc_deprecation', None)
101 101 if deprecation:
102 102 ret_value['DEPRECATION_WARNING'] = deprecation
103 103
104 104 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
105 105 content_type = 'application/json'
106 106 content_type_header = 'Content-Type'
107 107 headers = {
108 108 content_type_header: content_type
109 109 }
110 110 return Response(
111 111 body=raw_body,
112 112 content_type=content_type,
113 113 headerlist=[(k, v) for k, v in headers.items()]
114 114 )
115 115
116 116
117 117 def jsonrpc_error(request, message, retid=None, code: typing.Optional[int] = None, headers: typing.Optional[dict] = None):
118 118 """
119 119 Generate a Response object with a JSON-RPC error body
120 120 """
121 121 headers = headers or {}
122 122 content_type = 'application/json'
123 123 content_type_header = 'Content-Type'
124 124 if content_type_header not in headers:
125 125 headers[content_type_header] = content_type
126 126
127 127 err_dict = {'id': retid, 'result': None, 'error': message}
128 128 raw_body = render(DEFAULT_RENDERER, err_dict, request=request)
129 129
130 130 return Response(
131 131 body=raw_body,
132 132 status=code,
133 133 content_type=content_type,
134 134 headerlist=[(k, v) for k, v in headers.items()]
135 135 )
136 136
137 137
138 138 def exception_view(exc, request):
139 139 rpc_id = getattr(request, 'rpc_id', None)
140 140
141 141 if isinstance(exc, JSONRPCError):
142 142 fault_message = safe_str(exc.message)
143 143 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
144 144 elif isinstance(exc, JSONRPCValidationError):
145 145 colander_exc = exc.colander_exception
146 146 # TODO(marcink): think maybe of nicer way to serialize errors ?
147 147 fault_message = colander_exc.asdict()
148 148 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
149 149 elif isinstance(exc, JSONRPCForbidden):
150 150 fault_message = 'Access was denied to this resource.'
151 151 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
152 152 elif isinstance(exc, HTTPNotFound):
153 153 method = request.rpc_method
154 154 log.debug('json-rpc method `%s` not found in list of '
155 155 'api calls: %s, rpc_id:%s',
156 156 method, list(request.registry.jsonrpc_methods.keys()), rpc_id)
157 157
158 158 similar = 'none'
159 159 try:
160 160 similar_paterns = [f'*{x}*' for x in method.split('_')]
161 161 similar_found = find_methods(
162 162 request.registry.jsonrpc_methods, similar_paterns)
163 163 similar = ', '.join(similar_found.keys()) or similar
164 164 except Exception:
165 165 # make the whole above block safe
166 166 pass
167 167
168 168 fault_message = "No such method: {}. Similar methods: {}".format(
169 169 method, similar)
170 170 else:
171 171 fault_message = 'undefined error'
172 172 exc_info = exc.exc_info()
173 173 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
174 174
175 175 statsd = request.registry.statsd
176 176 if statsd:
177 177 exc_type = "{}.{}".format(exc.__class__.__module__, exc.__class__.__name__)
178 178 statsd.incr('rhodecode_exception_total',
179 179 tags=["exc_source:api", "type:{}".format(exc_type)])
180 180
181 181 return jsonrpc_error(request, fault_message, rpc_id)
182 182
183 183
184 184 def request_view(request):
185 185 """
186 186 Main request handling method. It handles all logic to call a specific
187 187 exposed method
188 188 """
189 189 # cython compatible inspect
190 190 from rhodecode.config.patches import inspect_getargspec
191 191 inspect = inspect_getargspec()
192 192
193 193 # check if we can find this session using api_key, get_by_auth_token
194 194 # search not expired tokens only
195 195 try:
196 196 api_user = User.get_by_auth_token(request.rpc_api_key)
197 197
198 198 if api_user is None:
199 199 return jsonrpc_error(
200 200 request, retid=request.rpc_id, message='Invalid API KEY')
201 201
202 202 if not api_user.active:
203 203 return jsonrpc_error(
204 204 request, retid=request.rpc_id,
205 205 message='Request from this user not allowed')
206 206
207 207 # check if we are allowed to use this IP
208 208 auth_u = AuthUser(
209 209 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
210 210 if not auth_u.ip_allowed:
211 211 return jsonrpc_error(
212 212 request, retid=request.rpc_id,
213 213 message='Request from IP:%s not allowed' % (
214 214 request.rpc_ip_addr,))
215 215 else:
216 216 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
217 217
218 218 # register our auth-user
219 219 request.rpc_user = auth_u
220 220 request.environ['rc_auth_user_id'] = str(auth_u.user_id)
221 221
222 222 # now check if token is valid for API
223 223 auth_token = request.rpc_api_key
224 224 token_match = api_user.authenticate_by_token(
225 225 auth_token, roles=[UserApiKeys.ROLE_API])
226 226 invalid_token = not token_match
227 227
228 228 log.debug('Checking if API KEY is valid with proper role')
229 229 if invalid_token:
230 230 return jsonrpc_error(
231 231 request, retid=request.rpc_id,
232 232 message='API KEY invalid or, has bad role for an API call')
233 233
234 234 except Exception:
235 235 log.exception('Error on API AUTH')
236 236 return jsonrpc_error(
237 237 request, retid=request.rpc_id, message='Invalid API KEY')
238 238
239 239 method = request.rpc_method
240 240 func = request.registry.jsonrpc_methods[method]
241 241
242 242 # now that we have a method, add request._req_params to
243 243 # self.kargs and dispatch control to WGIController
244 244
245 245 argspec = inspect.getargspec(func)
246 246 arglist = argspec[0]
247 247 defs = argspec[3] or []
248 248 defaults = [type(a) for a in defs]
249 249 default_empty = type(NotImplemented)
250 250
251 251 # kw arguments required by this method
252 252 func_kwargs = dict(itertools.zip_longest(
253 253 reversed(arglist), reversed(defaults), fillvalue=default_empty))
254 254
255 255 # This attribute will need to be first param of a method that uses
256 256 # api_key, which is translated to instance of user at that name
257 257 user_var = 'apiuser'
258 258 request_var = 'request'
259 259
260 260 for arg in [user_var, request_var]:
261 261 if arg not in arglist:
262 262 return jsonrpc_error(
263 263 request,
264 264 retid=request.rpc_id,
265 265 message='This method [%s] does not support '
266 266 'required parameter `%s`' % (func.__name__, arg))
267 267
268 268 # get our arglist and check if we provided them as args
269 269 for arg, default in func_kwargs.items():
270 270 if arg in [user_var, request_var]:
271 271 # user_var and request_var are pre-hardcoded parameters and we
272 272 # don't need to do any translation
273 273 continue
274 274
275 275 # skip the required param check if it's default value is
276 276 # NotImplementedType (default_empty)
277 277 if default == default_empty and arg not in request.rpc_params:
278 278 return jsonrpc_error(
279 279 request,
280 280 retid=request.rpc_id,
281 281 message=('Missing non optional `%s` arg in JSON DATA' % arg)
282 282 )
283 283
284 284 # sanitize extra passed arguments
285 285 for k in list(request.rpc_params.keys()):
286 286 if k not in func_kwargs:
287 287 del request.rpc_params[k]
288 288
289 289 call_params = request.rpc_params
290 290 call_params.update({
291 291 'request': request,
292 292 'apiuser': auth_u
293 293 })
294 294
295 295 # register some common functions for usage
296 296 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
297 297
298 298 statsd = request.registry.statsd
299 299
300 300 try:
301 301 ret_value = func(**call_params)
302 302 resp = jsonrpc_response(request, ret_value)
303 303 if statsd:
304 304 statsd.incr('rhodecode_api_call_success_total')
305 305 return resp
306 306 except JSONRPCBaseError:
307 307 raise
308 308 except Exception:
309 309 log.exception('Unhandled exception occurred on api call: %s', func)
310 310 exc_info = sys.exc_info()
311 311 exc_id, exc_type_name = store_exception(
312 312 id(exc_info), exc_info, prefix='rhodecode-api')
313 313 error_headers = {
314 314 'RhodeCode-Exception-Id': str(exc_id),
315 315 'RhodeCode-Exception-Type': str(exc_type_name)
316 316 }
317 317 err_resp = jsonrpc_error(
318 318 request, retid=request.rpc_id, message='Internal server error',
319 319 headers=error_headers)
320 320 if statsd:
321 321 statsd.incr('rhodecode_api_call_fail_total')
322 322 return err_resp
323 323
324 324
325 325 def setup_request(request):
326 326 """
327 327 Parse a JSON-RPC request body. It's used inside the predicates method
328 328 to validate and bootstrap requests for usage in rpc calls.
329 329
330 330 We need to raise JSONRPCError here if we want to return some errors back to
331 331 user.
332 332 """
333 333
334 334 log.debug('Executing setup request: %r', request)
335 335 request.rpc_ip_addr = get_ip_addr(request.environ)
336 336 # TODO(marcink): deprecate GET at some point
337 337 if request.method not in ['POST', 'GET']:
338 338 log.debug('unsupported request method "%s"', request.method)
339 339 raise JSONRPCError(
340 340 'unsupported request method "%s". Please use POST' % request.method)
341 341
342 342 if 'CONTENT_LENGTH' not in request.environ:
343 343 log.debug("No Content-Length")
344 344 raise JSONRPCError("Empty body, No Content-Length in request")
345 345
346 346 else:
347 347 length = request.environ['CONTENT_LENGTH']
348 348 log.debug('Content-Length: %s', length)
349 349
350 350 if length == 0:
351 351 log.debug("Content-Length is 0")
352 352 raise JSONRPCError("Content-Length is 0")
353 353
354 354 raw_body = request.body
355 355 log.debug("Loading JSON body now")
356 356 try:
357 357 json_body = ext_json.json.loads(raw_body)
358 358 except ValueError as e:
359 359 # catch JSON errors Here
360 360 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
361 361
362 362 request.rpc_id = json_body.get('id')
363 363 request.rpc_method = json_body.get('method')
364 364
365 365 # check required base parameters
366 366 try:
367 367 api_key = json_body.get('api_key')
368 368 if not api_key:
369 369 api_key = json_body.get('auth_token')
370 370
371 371 if not api_key:
372 372 raise KeyError('api_key or auth_token')
373 373
374 374 # TODO(marcink): support passing in token in request header
375 375
376 376 request.rpc_api_key = api_key
377 377 request.rpc_id = json_body['id']
378 378 request.rpc_method = json_body['method']
379 379 request.rpc_params = json_body['args'] \
380 380 if isinstance(json_body['args'], dict) else {}
381 381
382 382 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
383 383 except KeyError as e:
384 384 raise JSONRPCError(f'Incorrect JSON data. Missing {e}')
385 385
386 386 log.debug('setup complete, now handling method:%s rpcid:%s',
387 387 request.rpc_method, request.rpc_id, )
388 388
389 389
390 390 class RoutePredicate(object):
391 391 def __init__(self, val, config):
392 392 self.val = val
393 393
394 394 def text(self):
395 395 return f'jsonrpc route = {self.val}'
396 396
397 397 phash = text
398 398
399 399 def __call__(self, info, request):
400 400 if self.val:
401 401 # potentially setup and bootstrap our call
402 402 setup_request(request)
403 403
404 404 # Always return True so that even if it isn't a valid RPC it
405 405 # will fall through to the underlaying handlers like notfound_view
406 406 return True
407 407
408 408
409 409 class NotFoundPredicate(object):
410 410 def __init__(self, val, config):
411 411 self.val = val
412 412 self.methods = config.registry.jsonrpc_methods
413 413
414 414 def text(self):
415 415 return f'jsonrpc method not found = {self.val}'
416 416
417 417 phash = text
418 418
419 419 def __call__(self, info, request):
420 420 return hasattr(request, 'rpc_method')
421 421
422 422
423 423 class MethodPredicate(object):
424 424 def __init__(self, val, config):
425 425 self.method = val
426 426
427 427 def text(self):
428 428 return f'jsonrpc method = {self.method}'
429 429
430 430 phash = text
431 431
432 432 def __call__(self, context, request):
433 433 # we need to explicitly return False here, so pyramid doesn't try to
434 434 # execute our view directly. We need our main handler to execute things
435 435 return getattr(request, 'rpc_method') == self.method
436 436
437 437
438 438 def add_jsonrpc_method(config, view, **kwargs):
439 439 # pop the method name
440 440 method = kwargs.pop('method', None)
441 441
442 442 if method is None:
443 443 raise ConfigurationError(
444 444 'Cannot register a JSON-RPC method without specifying the "method"')
445 445
446 446 # we define custom predicate, to enable to detect conflicting methods,
447 447 # those predicates are kind of "translation" from the decorator variables
448 448 # to internal predicates names
449 449
450 450 kwargs['jsonrpc_method'] = method
451 451
452 452 # register our view into global view store for validation
453 453 config.registry.jsonrpc_methods[method] = view
454 454
455 455 # we're using our main request_view handler, here, so each method
456 456 # has a unified handler for itself
457 457 config.add_view(request_view, route_name='apiv2', **kwargs)
458 458
459 459
460 460 class jsonrpc_method(object):
461 461 """
462 462 decorator that works similar to @add_view_config decorator,
463 463 but tailored for our JSON RPC
464 464 """
465 465
466 466 venusian = venusian # for testing injection
467 467
468 468 def __init__(self, method=None, **kwargs):
469 469 self.method = method
470 470 self.kwargs = kwargs
471 471
472 472 def __call__(self, wrapped):
473 473 kwargs = self.kwargs.copy()
474 474 kwargs['method'] = self.method or wrapped.__name__
475 475 depth = kwargs.pop('_depth', 0)
476 476
477 477 def callback(context, name, ob):
478 478 config = context.config.with_package(info.module)
479 479 config.add_jsonrpc_method(view=ob, **kwargs)
480 480
481 481 info = venusian.attach(wrapped, callback, category='pyramid',
482 482 depth=depth + 1)
483 483 if info.scope == 'class':
484 484 # ensure that attr is set if decorating a class method
485 485 kwargs.setdefault('attr', wrapped.__name__)
486 486
487 487 kwargs['_info'] = info.codeinfo # fbo action_method
488 488 return wrapped
489 489
490 490
491 491 class jsonrpc_deprecated_method(object):
492 492 """
493 493 Marks method as deprecated, adds log.warning, and inject special key to
494 494 the request variable to mark method as deprecated.
495 495 Also injects special docstring that extract_docs will catch to mark
496 496 method as deprecated.
497 497
498 498 :param use_method: specify which method should be used instead of
499 499 the decorated one
500 500
501 501 Use like::
502 502
503 503 @jsonrpc_method()
504 504 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
505 505 def old_func(request, apiuser, arg1, arg2):
506 506 ...
507 507 """
508 508
509 509 def __init__(self, use_method, deprecated_at_version):
510 510 self.use_method = use_method
511 511 self.deprecated_at_version = deprecated_at_version
512 512 self.deprecated_msg = ''
513 513
514 514 def __call__(self, func):
515 515 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
516 516 method=self.use_method)
517 517
518 518 docstring = """\n
519 519 .. deprecated:: {version}
520 520
521 521 {deprecation_message}
522 522
523 523 {original_docstring}
524 524 """
525 525 func.__doc__ = docstring.format(
526 526 version=self.deprecated_at_version,
527 527 deprecation_message=self.deprecated_msg,
528 528 original_docstring=func.__doc__)
529 529 return decorator.decorator(self.__wrapper, func)
530 530
531 531 def __wrapper(self, func, *fargs, **fkwargs):
532 532 log.warning('DEPRECATED API CALL on function %s, please '
533 533 'use `%s` instead', func, self.use_method)
534 534 # alter function docstring to mark as deprecated, this is picked up
535 535 # via fabric file that generates API DOC.
536 536 result = func(*fargs, **fkwargs)
537 537
538 538 request = fargs[0]
539 539 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
540 540 return result
541 541
542 542
543 543 def add_api_methods(config):
544 544 from rhodecode.api.views import (
545 545 deprecated_api, gist_api, pull_request_api, repo_api, repo_group_api,
546 546 server_api, search_api, testing_api, user_api, user_group_api)
547 547
548 548 config.scan('rhodecode.api.views')
549 549
550 550
551 551 def includeme(config):
552 552 plugin_module = 'rhodecode.api'
553 553 plugin_settings = get_plugin_settings(
554 554 plugin_module, config.registry.settings)
555 555
556 556 if not hasattr(config.registry, 'jsonrpc_methods'):
557 557 config.registry.jsonrpc_methods = OrderedDict()
558 558
559 559 # match filter by given method only
560 560 config.add_view_predicate('jsonrpc_method', MethodPredicate)
561 561 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
562 562
563 563 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer())
564 564 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
565 565
566 566 config.add_route_predicate(
567 567 'jsonrpc_call', RoutePredicate)
568 568
569 569 config.add_route(
570 570 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
571 571
572 572 # register some exception handling view
573 573 config.add_view(exception_view, context=JSONRPCBaseError)
574 574 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
575 575
576 576 add_api_methods(config)
@@ -1,42 +1,42 b''
1 1
2 2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 class JSONRPCBaseError(Exception):
23 23 def __init__(self, message='', *args):
24 24 self.message = message
25 25 super(JSONRPCBaseError, self).__init__(message, *args)
26 26
27 27
28 28 class JSONRPCError(JSONRPCBaseError):
29 29 pass
30 30
31 31
32 32 class JSONRPCValidationError(JSONRPCBaseError):
33 33
34 34 def __init__(self, *args, **kwargs):
35 35 self.colander_exception = kwargs.pop('colander_exc')
36 36 super(JSONRPCValidationError, self).__init__(
37 37 message=self.colander_exception, *args)
38 38
39 39
40 40 class JSONRPCForbidden(JSONRPCBaseError):
41 41 pass
42 42
@@ -1,18 +1,18 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,51 +1,51 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.meta import Session
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.model.auth_token import AuthTokenModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26
27 27
28 28 @pytest.fixture(scope="class")
29 29 def testuser_api(request, baseapp):
30 30 cls = request.cls
31 31
32 32 # ADMIN USER
33 33 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
34 34 cls.apikey = cls.usr.api_key
35 35
36 36 # REGULAR USER
37 37 cls.test_user = UserModel().create_or_update(
38 38 username='test-api',
39 39 password='test',
40 40 email='test@api.rhodecode.org',
41 41 firstname='first',
42 42 lastname='last'
43 43 )
44 44 # create TOKEN for user, if he doesn't have one
45 45 if not cls.test_user.api_key:
46 46 AuthTokenModel().create(
47 47 user=cls.test_user, description=u'TEST_USER_TOKEN')
48 48
49 49 Session().commit()
50 50 cls.TEST_USER_LOGIN = cls.test_user.username
51 51 cls.apikey_regular = cls.test_user.api_key
@@ -1,61 +1,61 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.db import Repository, RepositoryField
23 23 from rhodecode.api.tests.utils import (
24 24 build_data, api_call, assert_ok, assert_error)
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestAddFieldToRepo(object):
29 29 def test_api_add_field_to_repo(self, backend):
30 30 repo = backend.create_repo()
31 31 repo_name = repo.repo_name
32 32 id_, params = build_data(
33 33 self.apikey, 'add_field_to_repo',
34 34 repoid=repo_name,
35 35 key='extra_field',
36 36 label='extra_field_label',
37 37 description='extra_field_desc')
38 38 response = api_call(self.app, params)
39 39 expected = {
40 40 'msg': 'Added new repository field `extra_field`',
41 41 'success': True,
42 42 }
43 43 assert_ok(id_, expected, given=response.body)
44 44
45 45 repo = Repository.get_by_repo_name(repo_name)
46 46 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
47 47 _data = repo_field.get_dict()
48 48 assert _data['field_desc'] == 'extra_field_desc'
49 49 assert _data['field_key'] == 'extra_field'
50 50 assert _data['field_label'] == 'extra_field_label'
51 51
52 52 id_, params = build_data(
53 53 self.apikey, 'add_field_to_repo',
54 54 repoid=repo_name,
55 55 key='extra_field',
56 56 label='extra_field_label',
57 57 description='extra_field_desc')
58 58 response = api_call(self.app, params)
59 59 expected = 'Field with key `extra_field` exists for repo `%s`' % (
60 60 repo_name)
61 61 assert_error(id_, expected, given=response.body)
@@ -1,71 +1,71 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.user_group import UserGroupModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestAddUserToUserGroup(object):
30 30 def test_api_add_user_to_user_group(self, user_util):
31 31 group = user_util.create_user_group()
32 32 user = user_util.create_user()
33 33 group_name = group.users_group_name
34 34 user_name = user.username
35 35 id_, params = build_data(
36 36 self.apikey, 'add_user_to_user_group',
37 37 usergroupid=group_name, userid=user_name)
38 38 response = api_call(self.app, params)
39 39 expected = {
40 40 'msg': 'added member `%s` to user group `%s`' % (
41 41 user_name, group_name
42 42 ),
43 43 'success': True
44 44 }
45 45 assert_ok(id_, expected, given=response.body)
46 46
47 47 def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util):
48 48 user = user_util.create_user()
49 49 user_name = user.username
50 50 id_, params = build_data(
51 51 self.apikey, 'add_user_to_user_group',
52 52 usergroupid='false-group',
53 53 userid=user_name)
54 54 response = api_call(self.app, params)
55 55
56 56 expected = 'user group `%s` does not exist' % 'false-group'
57 57 assert_error(id_, expected, given=response.body)
58 58
59 59 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
60 60 def test_api_add_user_to_user_group_exception_occurred(self, user_util):
61 61 group = user_util.create_user_group()
62 62 user = user_util.create_user()
63 63 group_name = group.users_group_name
64 64 user_name = user.username
65 65 id_, params = build_data(
66 66 self.apikey, 'add_user_to_user_group',
67 67 usergroupid=group_name, userid=user_name)
68 68 response = api_call(self.app, params)
69 69
70 70 expected = 'failed to add member to user group `%s`' % (group_name,)
71 71 assert_error(id_, expected, given=response.body)
@@ -1,133 +1,133 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.api.utils import Optional, OAttr
23 23 from rhodecode.api.tests.utils import (
24 24 build_data, api_call, assert_error, assert_ok)
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestApi(object):
29 29 maxDiff = None
30 30
31 31 def test_Optional_object(self):
32 32
33 33 option1 = Optional(None)
34 34 assert '<Optional:%s>' % (None,) == repr(option1)
35 35 assert option1() is None
36 36
37 37 assert 1 == Optional.extract(Optional(1))
38 38 assert 'example' == Optional.extract('example')
39 39
40 40 def test_Optional_OAttr(self):
41 41 option1 = Optional(OAttr('apiuser'))
42 42 assert 'apiuser' == Optional.extract(option1)
43 43
44 44 def test_OAttr_object(self):
45 45 oattr1 = OAttr('apiuser')
46 46 assert '<OptionalAttr:apiuser>' == repr(oattr1)
47 47 assert oattr1() == oattr1
48 48
49 49 def test_api_wrong_key(self):
50 50 id_, params = build_data('trololo', 'get_user')
51 51 response = api_call(self.app, params)
52 52
53 53 expected = 'Invalid API KEY'
54 54 assert_error(id_, expected, given=response.body)
55 55
56 56 def test_api_missing_non_optional_param(self):
57 57 id_, params = build_data(self.apikey, 'get_repo')
58 58 response = api_call(self.app, params)
59 59
60 60 expected = 'Missing non optional `repoid` arg in JSON DATA'
61 61 assert_error(id_, expected, given=response.body)
62 62
63 63 def test_api_missing_non_optional_param_args_null(self):
64 64 id_, params = build_data(self.apikey, 'get_repo')
65 65 params = params.replace(b'"args": {}', b'"args": null')
66 66 response = api_call(self.app, params)
67 67
68 68 expected = 'Missing non optional `repoid` arg in JSON DATA'
69 69 assert_error(id_, expected, given=response.body)
70 70
71 71 def test_api_missing_non_optional_param_args_bad(self):
72 72 id_, params = build_data(self.apikey, 'get_repo')
73 73 params = params.replace(b'"args": {}', b'"args": 1')
74 74 response = api_call(self.app, params)
75 75
76 76 expected = 'Missing non optional `repoid` arg in JSON DATA'
77 77 assert_error(id_, expected, given=response.body)
78 78
79 79 def test_api_non_existing_method(self, request):
80 80 id_, params = build_data(self.apikey, 'not_existing', args='xx')
81 81 response = api_call(self.app, params)
82 82 expected = 'No such method: not_existing. Similar methods: none'
83 83 assert_error(id_, expected, given=response.body)
84 84
85 85 def test_api_non_existing_method_have_similar(self, request):
86 86 id_, params = build_data(self.apikey, 'comment', args='xx')
87 87 response = api_call(self.app, params)
88 88 expected = 'No such method: comment. ' \
89 89 'Similar methods: changeset_comment, comment_pull_request, ' \
90 90 'get_pull_request_comments, comment_commit, edit_comment, ' \
91 91 'get_comment, get_repo_comments'
92 92 assert_error(id_, expected, given=response.body)
93 93
94 94 def test_api_disabled_user(self, request):
95 95
96 96 def set_active(active):
97 97 from rhodecode.model.db import Session, User
98 98 user = User.get_by_auth_token(self.apikey)
99 99 user.active = active
100 100 Session().add(user)
101 101 Session().commit()
102 102
103 103 request.addfinalizer(lambda: set_active(True))
104 104
105 105 set_active(False)
106 106 id_, params = build_data(self.apikey, 'test', args='xx')
107 107 response = api_call(self.app, params)
108 108 expected = 'Request from this user not allowed'
109 109 assert_error(id_, expected, given=response.body)
110 110
111 111 def test_api_args_is_null(self):
112 112 __, params = build_data(self.apikey, 'get_users', )
113 113 params = params.replace(b'"args": {}', b'"args": null')
114 114 response = api_call(self.app, params)
115 115 assert response.status == '200 OK'
116 116
117 117 def test_api_args_is_bad(self):
118 118 __, params = build_data(self.apikey, 'get_users', )
119 119 params = params.replace(b'"args": {}', b'"args": 1')
120 120 response = api_call(self.app, params)
121 121 assert response.status == '200 OK'
122 122
123 123 def test_api_args_different_args(self):
124 124 import string
125 125 expected = {
126 126 'ascii_letters': string.ascii_letters,
127 127 'ws': string.whitespace,
128 128 'printables': string.printable
129 129 }
130 130 id_, params = build_data(self.apikey, 'test', args=expected)
131 131 response = api_call(self.app, params)
132 132 assert response.status == '200 OK'
133 133 assert_ok(id_, expected, response.body)
@@ -1,44 +1,44 b''
1 1
2 2
3 # Copyright (C) 2017-2020 RhodeCode GmbH
3 # Copyright (C) 2017-2023 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.lib.user_sessions import FileAuthSessions
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestCleanupSessions(object):
31 31 def test_api_cleanup_sessions(self):
32 32 id_, params = build_data(self.apikey, 'cleanup_sessions')
33 33 response = api_call(self.app, params)
34 34
35 35 expected = {'backend': 'file sessions', 'sessions_removed': 0}
36 36 assert_ok(id_, expected, given=response.body)
37 37
38 38 @mock.patch.object(FileAuthSessions, 'clean_sessions', crash)
39 39 def test_api_cleanup_error(self):
40 40 id_, params = build_data(self.apikey, 'cleanup_sessions', )
41 41 response = api_call(self.app, params)
42 42
43 43 expected = 'Error occurred during session cleanup'
44 44 assert_error(id_, expected, given=response.body)
@@ -1,112 +1,112 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.db import UserLog
23 23 from rhodecode.model.pull_request import PullRequestModel
24 24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestClosePullRequest(object):
31 31
32 32 @pytest.mark.backends("git", "hg")
33 33 def test_api_close_pull_request(self, pr_util):
34 34 pull_request = pr_util.create_pull_request()
35 35 pull_request_id = pull_request.pull_request_id
36 36 author = pull_request.user_id
37 37 repo = pull_request.target_repo.repo_id
38 38 id_, params = build_data(
39 39 self.apikey, 'close_pull_request',
40 40 repoid=pull_request.target_repo.repo_name,
41 41 pullrequestid=pull_request.pull_request_id)
42 42 response = api_call(self.app, params)
43 43 expected = {
44 44 'pull_request_id': pull_request_id,
45 45 'close_status': 'Rejected',
46 46 'closed': True,
47 47 }
48 48 assert_ok(id_, expected, response.body)
49 49 journal = UserLog.query()\
50 50 .filter(UserLog.user_id == author) \
51 51 .order_by(UserLog.user_log_id.asc()) \
52 52 .filter(UserLog.repository_id == repo)\
53 53 .all()
54 54 assert journal[-1].action == 'repo.pull_request.close'
55 55
56 56 @pytest.mark.backends("git", "hg")
57 57 def test_api_close_pull_request_already_closed_error(self, pr_util):
58 58 pull_request = pr_util.create_pull_request()
59 59 pull_request_id = pull_request.pull_request_id
60 60 pull_request_repo = pull_request.target_repo.repo_name
61 61 PullRequestModel().close_pull_request(
62 62 pull_request, pull_request.author)
63 63 id_, params = build_data(
64 64 self.apikey, 'close_pull_request',
65 65 repoid=pull_request_repo, pullrequestid=pull_request_id)
66 66 response = api_call(self.app, params)
67 67
68 68 expected = 'pull request `%s` is already closed' % pull_request_id
69 69 assert_error(id_, expected, given=response.body)
70 70
71 71 @pytest.mark.backends("git", "hg")
72 72 def test_api_close_pull_request_repo_error(self, pr_util):
73 73 pull_request = pr_util.create_pull_request()
74 74 id_, params = build_data(
75 75 self.apikey, 'close_pull_request',
76 76 repoid=666, pullrequestid=pull_request.pull_request_id)
77 77 response = api_call(self.app, params)
78 78
79 79 expected = 'repository `666` does not exist'
80 80 assert_error(id_, expected, given=response.body)
81 81
82 82 @pytest.mark.backends("git", "hg")
83 83 def test_api_close_pull_request_non_admin_with_userid_error(self,
84 84 pr_util):
85 85 pull_request = pr_util.create_pull_request()
86 86 id_, params = build_data(
87 87 self.apikey_regular, 'close_pull_request',
88 88 repoid=pull_request.target_repo.repo_name,
89 89 pullrequestid=pull_request.pull_request_id,
90 90 userid=TEST_USER_ADMIN_LOGIN)
91 91 response = api_call(self.app, params)
92 92
93 93 expected = 'userid is not the same as your user'
94 94 assert_error(id_, expected, given=response.body)
95 95
96 96 @pytest.mark.backends("git", "hg")
97 97 def test_api_close_pull_request_no_perms_to_close(
98 98 self, user_util, pr_util):
99 99 user = user_util.create_user()
100 100 pull_request = pr_util.create_pull_request()
101 101
102 102 id_, params = build_data(
103 103 user.api_key, 'close_pull_request',
104 104 repoid=pull_request.target_repo.repo_name,
105 105 pullrequestid=pull_request.pull_request_id,)
106 106 response = api_call(self.app, params)
107 107
108 108 expected = ('pull request `%s` close failed, '
109 109 'no permission to close.') % pull_request.pull_request_id
110 110
111 111 response_json = response.json['error']
112 112 assert response_json == expected
@@ -1,115 +1,115 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.db import ChangesetStatus, User
23 23 from rhodecode.api.tests.utils import (
24 24 build_data, api_call, assert_error, assert_ok)
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestCommentCommit(object):
29 29 def test_api_comment_commit_on_empty_repo(self, backend):
30 30 repo = backend.create_repo()
31 31 id_, params = build_data(
32 32 self.apikey, 'comment_commit', repoid=repo.repo_name,
33 33 commit_id='tip', message='message', status_change=None)
34 34 response = api_call(self.app, params)
35 35 expected = 'There are no commits yet'
36 36 assert_error(id_, expected, given=response.body)
37 37
38 38 @pytest.mark.parametrize("commit_id, expected_err", [
39 39 ('abcabca', {'hg': 'Commit {commit} does not exist for `{repo}`',
40 40 'git': 'Commit {commit} does not exist for `{repo}`',
41 41 'svn': 'Commit id {commit} not understood.'}),
42 42 ('idontexist', {'hg': 'Commit {commit} does not exist for `{repo}`',
43 43 'git': 'Commit {commit} does not exist for `{repo}`',
44 44 'svn': 'Commit id {commit} not understood.'}),
45 45 ])
46 46 def test_api_comment_commit_wrong_hash(self, backend, commit_id, expected_err):
47 47 repo_name = backend.repo.repo_name
48 48 id_, params = build_data(
49 49 self.apikey, 'comment_commit', repoid=repo_name,
50 50 commit_id=commit_id, message='message', status_change=None)
51 51 response = api_call(self.app, params)
52 52
53 53 expected_err = expected_err[backend.alias]
54 54 expected_err = expected_err.format(
55 55 repo=backend.repo.scm_instance().name, commit=commit_id)
56 56 assert_error(id_, expected_err, given=response.body)
57 57
58 58 @pytest.mark.parametrize("status_change, message, commit_id", [
59 59 (None, 'Hallo', 'tip'),
60 60 (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'),
61 61 (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'),
62 62 ])
63 63 def test_api_comment_commit(
64 64 self, backend, status_change, message, commit_id,
65 65 no_notifications):
66 66
67 67 commit_id = backend.repo.scm_instance().get_commit(commit_id).raw_id
68 68
69 69 id_, params = build_data(
70 70 self.apikey, 'comment_commit', repoid=backend.repo_name,
71 71 commit_id=commit_id, message=message, status=status_change)
72 72 response = api_call(self.app, params)
73 73 repo = backend.repo.scm_instance()
74 74 expected = {
75 75 'msg': 'Commented on commit `%s` for repository `%s`' % (
76 76 repo.get_commit().raw_id, backend.repo_name),
77 77 'status_change': status_change,
78 78 'success': True
79 79 }
80 80 assert_ok(id_, expected, given=response.body)
81 81
82 82 def test_api_comment_commit_with_extra_recipients(self, backend, user_util):
83 83
84 84 commit_id = backend.repo.scm_instance().get_commit('tip').raw_id
85 85
86 86 user1 = user_util.create_user()
87 87 user1_id = user1.user_id
88 88 user2 = user_util.create_user()
89 89 user2_id = user2.user_id
90 90
91 91 id_, params = build_data(
92 92 self.apikey, 'comment_commit', repoid=backend.repo_name,
93 93 commit_id=commit_id,
94 94 message='abracadabra',
95 95 extra_recipients=[user1.user_id, user2.username])
96 96
97 97 response = api_call(self.app, params)
98 98 repo = backend.repo.scm_instance()
99 99
100 100 expected = {
101 101 'msg': 'Commented on commit `%s` for repository `%s`' % (
102 102 repo.get_commit().raw_id, backend.repo_name),
103 103 'status_change': None,
104 104 'success': True
105 105 }
106 106
107 107 assert_ok(id_, expected, given=response.body)
108 108 # check user1/user2 inbox for notification
109 109 user1 = User.get(user1_id)
110 110 assert 1 == len(user1.notifications)
111 111 assert 'abracadabra' in user1.notifications[0].notification.body
112 112
113 113 user2 = User.get(user2_id)
114 114 assert 1 == len(user2.notifications)
115 115 assert 'abracadabra' in user2.notifications[0].notification.body
@@ -1,380 +1,380 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.comment import CommentsModel
23 23 from rhodecode.model.db import UserLog, User, ChangesetComment
24 24 from rhodecode.model.pull_request import PullRequestModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestCommentPullRequest(object):
32 32 finalizers = []
33 33
34 34 def teardown_method(self, method):
35 35 if self.finalizers:
36 36 for finalizer in self.finalizers:
37 37 finalizer()
38 38 self.finalizers = []
39 39
40 40 @pytest.mark.backends("git", "hg")
41 41 def test_api_comment_pull_request(self, pr_util, no_notifications):
42 42 pull_request = pr_util.create_pull_request()
43 43 pull_request_id = pull_request.pull_request_id
44 44 author = pull_request.user_id
45 45 repo = pull_request.target_repo.repo_id
46 46 id_, params = build_data(
47 47 self.apikey, 'comment_pull_request',
48 48 repoid=pull_request.target_repo.repo_name,
49 49 pullrequestid=pull_request.pull_request_id,
50 50 message='test message')
51 51 response = api_call(self.app, params)
52 52 pull_request = PullRequestModel().get(pull_request.pull_request_id)
53 53
54 54 comments = CommentsModel().get_comments(
55 55 pull_request.target_repo.repo_id, pull_request=pull_request)
56 56
57 57 expected = {
58 58 'pull_request_id': pull_request.pull_request_id,
59 59 'comment_id': comments[-1].comment_id,
60 60 'status': {'given': None, 'was_changed': None}
61 61 }
62 62 assert_ok(id_, expected, response.body)
63 63
64 64 journal = UserLog.query()\
65 65 .filter(UserLog.user_id == author)\
66 66 .filter(UserLog.repository_id == repo) \
67 67 .order_by(UserLog.user_log_id.asc()) \
68 68 .all()
69 69 assert journal[-1].action == 'repo.pull_request.comment.create'
70 70
71 71 @pytest.mark.backends("git", "hg")
72 72 def test_api_comment_pull_request_with_extra_recipients(self, pr_util, user_util):
73 73 pull_request = pr_util.create_pull_request()
74 74
75 75 user1 = user_util.create_user()
76 76 user1_id = user1.user_id
77 77 user2 = user_util.create_user()
78 78 user2_id = user2.user_id
79 79
80 80 id_, params = build_data(
81 81 self.apikey, 'comment_pull_request',
82 82 repoid=pull_request.target_repo.repo_name,
83 83 pullrequestid=pull_request.pull_request_id,
84 84 message='test message',
85 85 extra_recipients=[user1.user_id, user2.username]
86 86 )
87 87 response = api_call(self.app, params)
88 88 pull_request = PullRequestModel().get(pull_request.pull_request_id)
89 89
90 90 comments = CommentsModel().get_comments(
91 91 pull_request.target_repo.repo_id, pull_request=pull_request)
92 92
93 93 expected = {
94 94 'pull_request_id': pull_request.pull_request_id,
95 95 'comment_id': comments[-1].comment_id,
96 96 'status': {'given': None, 'was_changed': None}
97 97 }
98 98 assert_ok(id_, expected, response.body)
99 99 # check user1/user2 inbox for notification
100 100 user1 = User.get(user1_id)
101 101 assert 1 == len(user1.notifications)
102 102 assert 'test message' in user1.notifications[0].notification.body
103 103
104 104 user2 = User.get(user2_id)
105 105 assert 1 == len(user2.notifications)
106 106 assert 'test message' in user2.notifications[0].notification.body
107 107
108 108 @pytest.mark.backends("git", "hg")
109 109 def test_api_comment_pull_request_change_status(
110 110 self, pr_util, no_notifications):
111 111 pull_request = pr_util.create_pull_request()
112 112 pull_request_id = pull_request.pull_request_id
113 113 id_, params = build_data(
114 114 self.apikey, 'comment_pull_request',
115 115 repoid=pull_request.target_repo.repo_name,
116 116 pullrequestid=pull_request.pull_request_id,
117 117 status='rejected')
118 118 response = api_call(self.app, params)
119 119 pull_request = PullRequestModel().get(pull_request_id)
120 120
121 121 comments = CommentsModel().get_comments(
122 122 pull_request.target_repo.repo_id, pull_request=pull_request)
123 123 expected = {
124 124 'pull_request_id': pull_request.pull_request_id,
125 125 'comment_id': comments[-1].comment_id,
126 126 'status': {'given': 'rejected', 'was_changed': True}
127 127 }
128 128 assert_ok(id_, expected, response.body)
129 129
130 130 @pytest.mark.backends("git", "hg")
131 131 def test_api_comment_pull_request_change_status_with_specific_commit_id_and_test_commit(
132 132 self, pr_util, no_notifications):
133 133 pull_request = pr_util.create_pull_request()
134 134 pull_request_id = pull_request.pull_request_id
135 135 latest_commit_id = 'test_commit'
136 136
137 137 # inject additional revision, to fail test the status change on
138 138 # non-latest commit
139 139 pull_request.revisions = pull_request.revisions + ['test_commit']
140 140
141 141 id_, params = build_data(
142 142 self.apikey, 'comment_pull_request',
143 143 message='test-change-of-status-not-allowed',
144 144 repoid=pull_request.target_repo.repo_name,
145 145 pullrequestid=pull_request.pull_request_id,
146 146 status='approved', commit_id=latest_commit_id)
147 147 response = api_call(self.app, params)
148 148 pull_request = PullRequestModel().get(pull_request_id)
149 149 comments = CommentsModel().get_comments(
150 150 pull_request.target_repo.repo_id, pull_request=pull_request)
151 151
152 152 expected = {
153 153 'pull_request_id': pull_request.pull_request_id,
154 154 'comment_id': comments[-1].comment_id,
155 155 'status': {'given': 'approved', 'was_changed': False}
156 156 }
157 157 assert_ok(id_, expected, response.body)
158 158
159 159 @pytest.mark.backends("git", "hg")
160 160 def test_api_comment_pull_request_change_status_with_specific_commit_id(
161 161 self, pr_util, no_notifications):
162 162 pull_request = pr_util.create_pull_request()
163 163 pull_request_id = pull_request.pull_request_id
164 164 latest_commit_id = pull_request.revisions[0]
165 165
166 166 id_, params = build_data(
167 167 self.apikey, 'comment_pull_request',
168 168 repoid=pull_request.target_repo.repo_name,
169 169 pullrequestid=pull_request.pull_request_id,
170 170 status='approved', commit_id=latest_commit_id)
171 171 response = api_call(self.app, params)
172 172 pull_request = PullRequestModel().get(pull_request_id)
173 173
174 174 comments = CommentsModel().get_comments(
175 175 pull_request.target_repo.repo_id, pull_request=pull_request)
176 176 expected = {
177 177 'pull_request_id': pull_request.pull_request_id,
178 178 'comment_id': comments[-1].comment_id,
179 179 'status': {'given': 'approved', 'was_changed': True}
180 180 }
181 181 assert_ok(id_, expected, response.body)
182 182
183 183 @pytest.mark.backends("git", "hg")
184 184 def test_api_comment_pull_request_missing_params_error(self, pr_util):
185 185 pull_request = pr_util.create_pull_request()
186 186 pull_request_id = pull_request.pull_request_id
187 187 pull_request_repo = pull_request.target_repo.repo_name
188 188 id_, params = build_data(
189 189 self.apikey, 'comment_pull_request',
190 190 repoid=pull_request_repo,
191 191 pullrequestid=pull_request_id)
192 192 response = api_call(self.app, params)
193 193
194 194 expected = 'Both message and status parameters are missing. At least one is required.'
195 195 assert_error(id_, expected, given=response.body)
196 196
197 197 @pytest.mark.backends("git", "hg")
198 198 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
199 199 pull_request = pr_util.create_pull_request()
200 200 pull_request_id = pull_request.pull_request_id
201 201 pull_request_repo = pull_request.target_repo.repo_name
202 202 id_, params = build_data(
203 203 self.apikey, 'comment_pull_request',
204 204 repoid=pull_request_repo,
205 205 pullrequestid=pull_request_id,
206 206 status='42')
207 207 response = api_call(self.app, params)
208 208
209 209 expected = 'Unknown comment status: `42`'
210 210 assert_error(id_, expected, given=response.body)
211 211
212 212 @pytest.mark.backends("git", "hg")
213 213 def test_api_comment_pull_request_repo_error(self, pr_util):
214 214 pull_request = pr_util.create_pull_request()
215 215 id_, params = build_data(
216 216 self.apikey, 'comment_pull_request',
217 217 repoid=666, pullrequestid=pull_request.pull_request_id)
218 218 response = api_call(self.app, params)
219 219
220 220 expected = 'repository `666` does not exist'
221 221 assert_error(id_, expected, given=response.body)
222 222
223 223 @pytest.mark.backends("git", "hg")
224 224 def test_api_comment_pull_request_non_admin_with_userid_error(self, pr_util):
225 225 pull_request = pr_util.create_pull_request()
226 226 id_, params = build_data(
227 227 self.apikey_regular, 'comment_pull_request',
228 228 repoid=pull_request.target_repo.repo_name,
229 229 pullrequestid=pull_request.pull_request_id,
230 230 userid=TEST_USER_ADMIN_LOGIN)
231 231 response = api_call(self.app, params)
232 232
233 233 expected = 'userid is not the same as your user'
234 234 assert_error(id_, expected, given=response.body)
235 235
236 236 @pytest.mark.backends("git", "hg")
237 237 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
238 238 pull_request = pr_util.create_pull_request()
239 239 id_, params = build_data(
240 240 self.apikey_regular, 'comment_pull_request',
241 241 repoid=pull_request.target_repo.repo_name,
242 242 status='approved',
243 243 pullrequestid=pull_request.pull_request_id,
244 244 commit_id='XXX')
245 245 response = api_call(self.app, params)
246 246
247 247 expected = 'Invalid commit_id `XXX` for this pull request.'
248 248 assert_error(id_, expected, given=response.body)
249 249
250 250 @pytest.mark.backends("git", "hg")
251 251 def test_api_edit_comment(self, pr_util):
252 252 pull_request = pr_util.create_pull_request()
253 253
254 254 id_, params = build_data(
255 255 self.apikey,
256 256 'comment_pull_request',
257 257 repoid=pull_request.target_repo.repo_name,
258 258 pullrequestid=pull_request.pull_request_id,
259 259 message='test message',
260 260 )
261 261 response = api_call(self.app, params)
262 262 json_response = response.json
263 263 comment_id = json_response['result']['comment_id']
264 264
265 265 message_after_edit = 'just message'
266 266 id_, params = build_data(
267 267 self.apikey,
268 268 'edit_comment',
269 269 comment_id=comment_id,
270 270 message=message_after_edit,
271 271 version=0,
272 272 )
273 273 response = api_call(self.app, params)
274 274 json_response = response.json
275 275 assert json_response['result']['version'] == 1
276 276
277 277 text_form_db = ChangesetComment.get(comment_id).text
278 278 assert message_after_edit == text_form_db
279 279
280 280 @pytest.mark.backends("git", "hg")
281 281 def test_api_edit_comment_wrong_version_mismatch(self, pr_util):
282 282 pull_request = pr_util.create_pull_request()
283 283
284 284 id_, params = build_data(
285 285 self.apikey, 'comment_pull_request',
286 286 repoid=pull_request.target_repo.repo_name,
287 287 pullrequestid=pull_request.pull_request_id,
288 288 message='test message')
289 289 response = api_call(self.app, params)
290 290 json_response = response.json
291 291 comment_id = json_response['result']['comment_id']
292 292
293 293 message_after_edit = 'just message'
294 294 id_, params = build_data(
295 295 self.apikey,
296 296 'edit_comment',
297 297 comment_id=comment_id,
298 298 message=message_after_edit,
299 299 version=1,
300 300 )
301 301 response = api_call(self.app, params)
302 302 expected = 'comment ({}) version ({}) mismatch'.format(comment_id, 1)
303 303 assert_error(id_, expected, given=response.body)
304 304
305 305 @pytest.mark.backends("git", "hg")
306 306 def test_api_edit_comment_wrong_version(self, pr_util):
307 307 pull_request = pr_util.create_pull_request()
308 308
309 309 id_, params = build_data(
310 310 self.apikey, 'comment_pull_request',
311 311 repoid=pull_request.target_repo.repo_name,
312 312 pullrequestid=pull_request.pull_request_id,
313 313 message='test message')
314 314 response = api_call(self.app, params)
315 315 json_response = response.json
316 316 comment_id = json_response['result']['comment_id']
317 317
318 318 id_, params = build_data(
319 319 self.apikey,
320 320 'edit_comment',
321 321 comment_id=comment_id,
322 322 message='',
323 323 version=0,
324 324 )
325 325 response = api_call(self.app, params)
326 326 expected = f"comment ({comment_id}) can't be changed with empty string"
327 327 assert_error(id_, expected, given=response.body)
328 328
329 329 @pytest.mark.backends("git", "hg")
330 330 def test_api_edit_comment_wrong_user_set_by_non_admin(self, pr_util):
331 331 pull_request = pr_util.create_pull_request()
332 332 pull_request_id = pull_request.pull_request_id
333 333 id_, params = build_data(
334 334 self.apikey,
335 335 'comment_pull_request',
336 336 repoid=pull_request.target_repo.repo_name,
337 337 pullrequestid=pull_request_id,
338 338 message='test message'
339 339 )
340 340 response = api_call(self.app, params)
341 341 json_response = response.json
342 342 comment_id = json_response['result']['comment_id']
343 343
344 344 id_, params = build_data(
345 345 self.apikey_regular,
346 346 'edit_comment',
347 347 comment_id=comment_id,
348 348 message='just message',
349 349 version=0,
350 350 userid=TEST_USER_ADMIN_LOGIN
351 351 )
352 352 response = api_call(self.app, params)
353 353 expected = 'userid is not the same as your user'
354 354 assert_error(id_, expected, given=response.body)
355 355
356 356 @pytest.mark.backends("git", "hg")
357 357 def test_api_edit_comment_wrong_user_with_permissions_to_edit_comment(self, pr_util):
358 358 pull_request = pr_util.create_pull_request()
359 359 pull_request_id = pull_request.pull_request_id
360 360 id_, params = build_data(
361 361 self.apikey,
362 362 'comment_pull_request',
363 363 repoid=pull_request.target_repo.repo_name,
364 364 pullrequestid=pull_request_id,
365 365 message='test message'
366 366 )
367 367 response = api_call(self.app, params)
368 368 json_response = response.json
369 369 comment_id = json_response['result']['comment_id']
370 370
371 371 id_, params = build_data(
372 372 self.apikey_regular,
373 373 'edit_comment',
374 374 comment_id=comment_id,
375 375 message='just message',
376 376 version=0,
377 377 )
378 378 response = api_call(self.app, params)
379 379 expected = "you don't have access to edit this comment"
380 380 assert_error(id_, expected, given=response.body)
@@ -1,101 +1,101 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.db import Gist
24 24 from rhodecode.model.gist import GistModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27 from rhodecode.tests.fixture import Fixture
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestApiCreateGist(object):
32 32 @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [
33 33 (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC),
34 34 (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE),
35 35 (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC),
36 36 (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE),
37 37 ])
38 38 def test_api_create_gist(self, lifetime, gist_type, gist_acl_level):
39 39 id_, params = build_data(
40 40 self.apikey_regular, 'create_gist',
41 41 lifetime=lifetime,
42 42 description='foobar-gist',
43 43 gist_type=gist_type,
44 44 acl_level=gist_acl_level,
45 45 files={'foobar_ąć': {'content': 'foo'}})
46 46 response = api_call(self.app, params)
47 47 response_json = response.json
48 48 gist = response_json['result']['gist']
49 49 expected = {
50 50 'gist': {
51 51 'access_id': gist['access_id'],
52 52 'created_on': gist['created_on'],
53 53 'modified_at': gist['modified_at'],
54 54 'description': 'foobar-gist',
55 55 'expires': gist['expires'],
56 56 'gist_id': gist['gist_id'],
57 57 'type': gist_type,
58 58 'url': gist['url'],
59 59 # content is empty since we don't show it here
60 60 'content': None,
61 61 'acl_level': gist_acl_level,
62 62 },
63 63 'msg': 'created new gist'
64 64 }
65 65 try:
66 66 assert_ok(id_, expected, given=response.body)
67 67 finally:
68 68 Fixture().destroy_gists()
69 69
70 70 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
71 71 ({'gist_type': '"ups" is not one of private, public'},
72 72 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
73 73
74 74 ({'lifetime': '-120 is less than minimum value -1'},
75 75 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
76 76
77 77 ({'0.content': 'Required'},
78 78 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
79 79 ])
80 80 def test_api_try_create_gist(
81 81 self, expected, lifetime, gist_type, gist_acl_level, files):
82 82 id_, params = build_data(
83 83 self.apikey_regular, 'create_gist',
84 84 lifetime=lifetime,
85 85 description='foobar-gist',
86 86 gist_type=gist_type,
87 87 acl_level=gist_acl_level,
88 88 files=files)
89 89 response = api_call(self.app, params)
90 90
91 91 try:
92 92 assert_error(id_, expected, given=response.body)
93 93 finally:
94 94 Fixture().destroy_gists()
95 95
96 96 @mock.patch.object(GistModel, 'create', crash)
97 97 def test_api_create_gist_exception_occurred(self):
98 98 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
99 99 response = api_call(self.app, params)
100 100 expected = 'failed to create gist'
101 101 assert_error(id_, expected, given=response.body)
@@ -1,367 +1,367 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.db import User
23 23 from rhodecode.model.pull_request import PullRequestModel
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
27 27 from rhodecode.api.tests.utils import build_data, api_call, assert_error
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestCreatePullRequestApi(object):
32 32 finalizers = []
33 33
34 34 def teardown_method(self, method):
35 35 if self.finalizers:
36 36 for finalizer in self.finalizers:
37 37 finalizer()
38 38 self.finalizers = []
39 39
40 40 def test_create_with_wrong_data(self):
41 41 required_data = {
42 42 'source_repo': 'tests/source_repo',
43 43 'target_repo': 'tests/target_repo',
44 44 'source_ref': 'branch:default:initial',
45 45 'target_ref': 'branch:default:new-feature',
46 46 }
47 47 for key in required_data:
48 48 data = required_data.copy()
49 49 data.pop(key)
50 50 id_, params = build_data(
51 51 self.apikey, 'create_pull_request', **data)
52 52 response = api_call(self.app, params)
53 53
54 54 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
55 55 assert_error(id_, expected, given=response.body)
56 56
57 57 @pytest.mark.backends("git", "hg")
58 58 @pytest.mark.parametrize('source_ref', [
59 59 'bookmarg:default:initial'
60 60 ])
61 61 def test_create_with_wrong_refs_data(self, backend, source_ref):
62 62
63 63 data = self._prepare_data(backend)
64 64 data['source_ref'] = source_ref
65 65
66 66 id_, params = build_data(
67 67 self.apikey_regular, 'create_pull_request', **data)
68 68
69 69 response = api_call(self.app, params)
70 70
71 71 expected = "Ref `{}` type is not allowed. " \
72 72 "Only:['bookmark', 'book', 'tag', 'branch'] " \
73 73 "are possible.".format(source_ref)
74 74 assert_error(id_, expected, given=response.body)
75 75
76 76 @pytest.mark.backends("git", "hg")
77 77 def test_create_with_correct_data(self, backend):
78 78 data = self._prepare_data(backend)
79 79 RepoModel().revoke_user_permission(
80 80 self.source.repo_name, User.DEFAULT_USER)
81 81 id_, params = build_data(
82 82 self.apikey_regular, 'create_pull_request', **data)
83 83 response = api_call(self.app, params)
84 84 expected_message = "Created new pull request `{title}`".format(
85 85 title=data['title'])
86 86 result = response.json
87 87 assert result['error'] is None
88 88 assert result['result']['msg'] == expected_message
89 89 pull_request_id = result['result']['pull_request_id']
90 90 pull_request = PullRequestModel().get(pull_request_id)
91 91 assert pull_request.title == data['title']
92 92 assert pull_request.description == data['description']
93 93 assert pull_request.source_ref == data['source_ref']
94 94 assert pull_request.target_ref == data['target_ref']
95 95 assert pull_request.source_repo.repo_name == data['source_repo']
96 96 assert pull_request.target_repo.repo_name == data['target_repo']
97 97 assert pull_request.revisions == [self.commit_ids['change']]
98 98 assert len(pull_request.reviewers) == 1
99 99
100 100 @pytest.mark.backends("git", "hg")
101 101 def test_create_with_empty_description(self, backend):
102 102 data = self._prepare_data(backend)
103 103 data.pop('description')
104 104 id_, params = build_data(
105 105 self.apikey_regular, 'create_pull_request', **data)
106 106 response = api_call(self.app, params)
107 107 expected_message = "Created new pull request `{title}`".format(
108 108 title=data['title'])
109 109 result = response.json
110 110 assert result['error'] is None
111 111 assert result['result']['msg'] == expected_message
112 112 pull_request_id = result['result']['pull_request_id']
113 113 pull_request = PullRequestModel().get(pull_request_id)
114 114 assert pull_request.description == ''
115 115
116 116 @pytest.mark.backends("git", "hg")
117 117 def test_create_with_empty_title(self, backend):
118 118 data = self._prepare_data(backend)
119 119 data.pop('title')
120 120 id_, params = build_data(
121 121 self.apikey_regular, 'create_pull_request', **data)
122 122 response = api_call(self.app, params)
123 123 result = response.json
124 124 pull_request_id = result['result']['pull_request_id']
125 125 pull_request = PullRequestModel().get(pull_request_id)
126 126 data['ref'] = backend.default_branch_name
127 127 title = '{source_repo}#{ref} to {target_repo}'.format(**data)
128 128 assert pull_request.title == title
129 129
130 130 @pytest.mark.backends("git", "hg")
131 131 def test_create_with_reviewers_specified_by_names(
132 132 self, backend, no_notifications):
133 133 data = self._prepare_data(backend)
134 134 reviewers = [
135 135 {'username': TEST_USER_REGULAR_LOGIN,
136 136 'reasons': ['{} added manually'.format(TEST_USER_REGULAR_LOGIN)]},
137 137 {'username': TEST_USER_ADMIN_LOGIN,
138 138 'reasons': ['{} added manually'.format(TEST_USER_ADMIN_LOGIN)],
139 139 'mandatory': True},
140 140 ]
141 141 data['reviewers'] = reviewers
142 142
143 143 id_, params = build_data(
144 144 self.apikey_regular, 'create_pull_request', **data)
145 145 response = api_call(self.app, params)
146 146
147 147 expected_message = "Created new pull request `{title}`".format(
148 148 title=data['title'])
149 149 result = response.json
150 150 assert result['error'] is None
151 151 assert result['result']['msg'] == expected_message
152 152 pull_request_id = result['result']['pull_request_id']
153 153 pull_request = PullRequestModel().get(pull_request_id)
154 154
155 155 actual_reviewers = []
156 156 for rev in pull_request.reviewers:
157 157 entry = {
158 158 'username': rev.user.username,
159 159 'reasons': rev.reasons,
160 160 }
161 161 if rev.mandatory:
162 162 entry['mandatory'] = rev.mandatory
163 163 actual_reviewers.append(entry)
164 164
165 165 owner_username = pull_request.target_repo.user.username
166 166 for spec_reviewer in reviewers[::]:
167 167 # default reviewer will be added who is an owner of the repo
168 168 # this get's overridden by a add owner to reviewers rule
169 169 if spec_reviewer['username'] == owner_username:
170 170 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
171 171 # since owner is more important, we don't inherit mandatory flag
172 172 del spec_reviewer['mandatory']
173 173
174 174 assert sorted(actual_reviewers, key=lambda e: e['username']) \
175 175 == sorted(reviewers, key=lambda e: e['username'])
176 176
177 177 @pytest.mark.backends("git", "hg")
178 178 def test_create_with_reviewers_specified_by_ids(
179 179 self, backend, no_notifications):
180 180 data = self._prepare_data(backend)
181 181 reviewers = [
182 182 {'username': UserModel().get_by_username(
183 183 TEST_USER_REGULAR_LOGIN).user_id,
184 184 'reasons': ['added manually']},
185 185 {'username': UserModel().get_by_username(
186 186 TEST_USER_ADMIN_LOGIN).user_id,
187 187 'reasons': ['added manually']},
188 188 ]
189 189
190 190 data['reviewers'] = reviewers
191 191 id_, params = build_data(
192 192 self.apikey_regular, 'create_pull_request', **data)
193 193 response = api_call(self.app, params)
194 194
195 195 expected_message = "Created new pull request `{title}`".format(
196 196 title=data['title'])
197 197 result = response.json
198 198 assert result['error'] is None
199 199 assert result['result']['msg'] == expected_message
200 200 pull_request_id = result['result']['pull_request_id']
201 201 pull_request = PullRequestModel().get(pull_request_id)
202 202
203 203 actual_reviewers = []
204 204 for rev in pull_request.reviewers:
205 205 entry = {
206 206 'username': rev.user.user_id,
207 207 'reasons': rev.reasons,
208 208 }
209 209 if rev.mandatory:
210 210 entry['mandatory'] = rev.mandatory
211 211 actual_reviewers.append(entry)
212 212
213 213 owner_user_id = pull_request.target_repo.user.user_id
214 214 for spec_reviewer in reviewers[::]:
215 215 # default reviewer will be added who is an owner of the repo
216 216 # this get's overridden by a add owner to reviewers rule
217 217 if spec_reviewer['username'] == owner_user_id:
218 218 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
219 219
220 220 assert sorted(actual_reviewers, key=lambda e: e['username']) \
221 221 == sorted(reviewers, key=lambda e: e['username'])
222 222
223 223 @pytest.mark.backends("git", "hg")
224 224 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
225 225 data = self._prepare_data(backend)
226 226 data['reviewers'] = [{'username': 'somebody'}]
227 227 id_, params = build_data(
228 228 self.apikey_regular, 'create_pull_request', **data)
229 229 response = api_call(self.app, params)
230 230 expected_message = 'user `somebody` does not exist'
231 231 assert_error(id_, expected_message, given=response.body)
232 232
233 233 @pytest.mark.backends("git", "hg")
234 234 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
235 235 data = self._prepare_data(backend)
236 236 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
237 237 data['reviewers'] = reviewers
238 238 id_, params = build_data(
239 239 self.apikey_regular, 'create_pull_request', **data)
240 240 response = api_call(self.app, params)
241 241 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
242 242 assert_error(id_, expected_message, given=response.body)
243 243
244 244 @pytest.mark.backends("git", "hg")
245 245 def test_create_with_no_commit_hashes(self, backend):
246 246 data = self._prepare_data(backend)
247 247 expected_source_ref = data['source_ref']
248 248 expected_target_ref = data['target_ref']
249 249 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
250 250 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
251 251 id_, params = build_data(
252 252 self.apikey_regular, 'create_pull_request', **data)
253 253 response = api_call(self.app, params)
254 254 expected_message = "Created new pull request `{title}`".format(
255 255 title=data['title'])
256 256 result = response.json
257 257 assert result['result']['msg'] == expected_message
258 258 pull_request_id = result['result']['pull_request_id']
259 259 pull_request = PullRequestModel().get(pull_request_id)
260 260 assert pull_request.source_ref == expected_source_ref
261 261 assert pull_request.target_ref == expected_target_ref
262 262
263 263 @pytest.mark.backends("git", "hg")
264 264 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
265 265 def test_create_fails_with_wrong_repo(self, backend, data_key):
266 266 repo_name = 'fake-repo'
267 267 data = self._prepare_data(backend)
268 268 data[data_key] = repo_name
269 269 id_, params = build_data(
270 270 self.apikey_regular, 'create_pull_request', **data)
271 271 response = api_call(self.app, params)
272 272 expected_message = 'repository `{}` does not exist'.format(repo_name)
273 273 assert_error(id_, expected_message, given=response.body)
274 274
275 275 @pytest.mark.backends("git", "hg")
276 276 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
277 277 def test_create_fails_with_non_existing_branch(self, backend, data_key):
278 278 branch_name = 'test-branch'
279 279 data = self._prepare_data(backend)
280 280 data[data_key] = "branch:{}".format(branch_name)
281 281 id_, params = build_data(
282 282 self.apikey_regular, 'create_pull_request', **data)
283 283 response = api_call(self.app, params)
284 284 expected_message = 'The specified value:{type}:`{name}` ' \
285 285 'does not exist, or is not allowed.'.format(type='branch',
286 286 name=branch_name)
287 287 assert_error(id_, expected_message, given=response.body)
288 288
289 289 @pytest.mark.backends("git", "hg")
290 290 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
291 291 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
292 292 data = self._prepare_data(backend)
293 293 ref = 'stange-ref'
294 294 data[data_key] = ref
295 295 id_, params = build_data(
296 296 self.apikey_regular, 'create_pull_request', **data)
297 297 response = api_call(self.app, params)
298 298 expected_message = (
299 299 'Ref `{ref}` given in a wrong format. Please check the API'
300 300 ' documentation for more details'.format(ref=ref))
301 301 assert_error(id_, expected_message, given=response.body)
302 302
303 303 @pytest.mark.backends("git", "hg")
304 304 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
305 305 def test_create_fails_with_non_existing_ref(self, backend, data_key):
306 306 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
307 307 ref = self._get_full_ref(backend, commit_id)
308 308 data = self._prepare_data(backend)
309 309 data[data_key] = ref
310 310 id_, params = build_data(
311 311 self.apikey_regular, 'create_pull_request', **data)
312 312 response = api_call(self.app, params)
313 313 expected_message = 'Ref `{}` does not exist'.format(ref)
314 314 assert_error(id_, expected_message, given=response.body)
315 315
316 316 @pytest.mark.backends("git", "hg")
317 317 def test_create_fails_when_no_revisions(self, backend):
318 318 data = self._prepare_data(backend, source_head='initial')
319 319 id_, params = build_data(
320 320 self.apikey_regular, 'create_pull_request', **data)
321 321 response = api_call(self.app, params)
322 322 expected_message = 'no commits found for merge between specified references'
323 323 assert_error(id_, expected_message, given=response.body)
324 324
325 325 @pytest.mark.backends("git", "hg")
326 326 def test_create_fails_when_no_permissions(self, backend):
327 327 data = self._prepare_data(backend)
328 328 RepoModel().revoke_user_permission(
329 329 self.source.repo_name, self.test_user)
330 330 RepoModel().revoke_user_permission(
331 331 self.source.repo_name, User.DEFAULT_USER)
332 332
333 333 id_, params = build_data(
334 334 self.apikey_regular, 'create_pull_request', **data)
335 335 response = api_call(self.app, params)
336 336 expected_message = 'repository `{}` does not exist'.format(
337 337 self.source.repo_name)
338 338 assert_error(id_, expected_message, given=response.body)
339 339
340 340 def _prepare_data(
341 341 self, backend, source_head='change', target_head='initial'):
342 342 commits = [
343 343 {'message': 'initial'},
344 344 {'message': 'change'},
345 345 {'message': 'new-feature', 'parents': ['initial']},
346 346 ]
347 347 self.commit_ids = backend.create_master_repo(commits)
348 348 self.source = backend.create_repo(heads=[source_head])
349 349 self.target = backend.create_repo(heads=[target_head])
350 350
351 351 data = {
352 352 'source_repo': self.source.repo_name,
353 353 'target_repo': self.target.repo_name,
354 354 'source_ref': self._get_full_ref(
355 355 backend, self.commit_ids[source_head]),
356 356 'target_ref': self._get_full_ref(
357 357 backend, self.commit_ids[target_head]),
358 358 'title': 'Test PR 1',
359 359 'description': 'Test'
360 360 }
361 361 RepoModel().grant_user_permission(
362 362 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
363 363 return data
364 364
365 365 def _get_full_ref(self, backend, commit_id):
366 366 return 'branch:{branch}:{commit_id}'.format(
367 367 branch=backend.default_branch_name, commit_id=commit_id)
@@ -1,348 +1,348 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.lib.vcs import settings
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo import RepoModel
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 28 from rhodecode.api.tests.utils import (
29 29 build_data, api_call, assert_ok, assert_error, crash)
30 30 from rhodecode.tests.fixture import Fixture
31 31 from rhodecode.lib.ext_json import json
32 32 from rhodecode.lib.str_utils import safe_str
33 33
34 34
35 35 fixture = Fixture()
36 36
37 37
38 38 @pytest.mark.usefixtures("testuser_api", "app")
39 39 class TestCreateRepo(object):
40 40
41 41 @pytest.mark.parametrize('given, expected_name, expected_exc', [
42 42 ('api repo-1', 'api-repo-1', False),
43 43 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
44 44 (u'unicode-ąć', u'unicode-ąć', False),
45 45 ('some repo v1.2', 'some-repo-v1.2', False),
46 46 ('v2.0', 'v2.0', False),
47 47 ])
48 48 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
49 49
50 50 id_, params = build_data(
51 51 self.apikey,
52 52 'create_repo',
53 53 repo_name=given,
54 54 owner=TEST_USER_ADMIN_LOGIN,
55 55 repo_type=backend.alias,
56 56 )
57 57 response = api_call(self.app, params)
58 58
59 59 ret = {
60 60 'msg': 'Created new repository `%s`' % (expected_name,),
61 61 'success': True,
62 62 'task': None,
63 63 }
64 64 expected = ret
65 65 assert_ok(id_, expected, given=response.body)
66 66
67 67 repo = RepoModel().get_by_repo_name(safe_str(expected_name))
68 68 assert repo is not None
69 69
70 70 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
71 71 response = api_call(self.app, params)
72 72 body = json.loads(response.body)
73 73
74 74 assert body['result']['enable_downloads'] is False
75 75 assert body['result']['enable_locking'] is False
76 76 assert body['result']['enable_statistics'] is False
77 77
78 78 fixture.destroy_repo(safe_str(expected_name))
79 79
80 80 def test_api_create_restricted_repo_type(self, backend):
81 81 repo_name = 'api-repo-type-{0}'.format(backend.alias)
82 82 id_, params = build_data(
83 83 self.apikey,
84 84 'create_repo',
85 85 repo_name=repo_name,
86 86 owner=TEST_USER_ADMIN_LOGIN,
87 87 repo_type=backend.alias,
88 88 )
89 89 git_backend = settings.BACKENDS['git']
90 90 with mock.patch(
91 91 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
92 92 response = api_call(self.app, params)
93 93
94 94 repo = RepoModel().get_by_repo_name(repo_name)
95 95
96 96 if backend.alias == 'git':
97 97 assert repo is not None
98 98 expected = {
99 99 'msg': 'Created new repository `{0}`'.format(repo_name,),
100 100 'success': True,
101 101 'task': None,
102 102 }
103 103 assert_ok(id_, expected, given=response.body)
104 104 else:
105 105 assert repo is None
106 106
107 107 fixture.destroy_repo(repo_name)
108 108
109 109 def test_api_create_repo_with_booleans(self, backend):
110 110 repo_name = 'api-repo-2'
111 111 id_, params = build_data(
112 112 self.apikey,
113 113 'create_repo',
114 114 repo_name=repo_name,
115 115 owner=TEST_USER_ADMIN_LOGIN,
116 116 repo_type=backend.alias,
117 117 enable_statistics=True,
118 118 enable_locking=True,
119 119 enable_downloads=True
120 120 )
121 121 response = api_call(self.app, params)
122 122
123 123 repo = RepoModel().get_by_repo_name(repo_name)
124 124
125 125 assert repo is not None
126 126 ret = {
127 127 'msg': 'Created new repository `%s`' % (repo_name,),
128 128 'success': True,
129 129 'task': None,
130 130 }
131 131 expected = ret
132 132 assert_ok(id_, expected, given=response.body)
133 133
134 134 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
135 135 response = api_call(self.app, params)
136 136 body = json.loads(response.body)
137 137
138 138 assert body['result']['enable_downloads'] is True
139 139 assert body['result']['enable_locking'] is True
140 140 assert body['result']['enable_statistics'] is True
141 141
142 142 fixture.destroy_repo(repo_name)
143 143
144 144 def test_api_create_repo_in_group(self, backend):
145 145 repo_group_name = 'my_gr'
146 146 # create the parent
147 147 fixture.create_repo_group(repo_group_name)
148 148
149 149 repo_name = '%s/api-repo-gr' % (repo_group_name,)
150 150 id_, params = build_data(
151 151 self.apikey, 'create_repo',
152 152 repo_name=repo_name,
153 153 owner=TEST_USER_ADMIN_LOGIN,
154 154 repo_type=backend.alias,)
155 155 response = api_call(self.app, params)
156 156 repo = RepoModel().get_by_repo_name(repo_name)
157 157 assert repo is not None
158 158 assert repo.group is not None
159 159
160 160 ret = {
161 161 'msg': 'Created new repository `%s`' % (repo_name,),
162 162 'success': True,
163 163 'task': None,
164 164 }
165 165 expected = ret
166 166 assert_ok(id_, expected, given=response.body)
167 167 fixture.destroy_repo(repo_name)
168 168 fixture.destroy_repo_group(repo_group_name)
169 169
170 170 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
171 171 repo_group_name = 'fake_group'
172 172
173 173 repo_name = '%s/api-repo-gr' % (repo_group_name,)
174 174 id_, params = build_data(
175 175 self.apikey, 'create_repo',
176 176 repo_name=repo_name,
177 177 owner=TEST_USER_ADMIN_LOGIN,
178 178 repo_type=backend.alias,)
179 179 response = api_call(self.app, params)
180 180
181 181 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
182 182 repo_group_name)}
183 183 assert_error(id_, expected, given=response.body)
184 184
185 185 def test_api_create_repo_unknown_owner(self, backend):
186 186 repo_name = 'api-repo-2'
187 187 owner = 'i-dont-exist'
188 188 id_, params = build_data(
189 189 self.apikey, 'create_repo',
190 190 repo_name=repo_name,
191 191 owner=owner,
192 192 repo_type=backend.alias)
193 193 response = api_call(self.app, params)
194 194 expected = 'user `%s` does not exist' % (owner,)
195 195 assert_error(id_, expected, given=response.body)
196 196
197 197 def test_api_create_repo_dont_specify_owner(self, backend):
198 198 repo_name = 'api-repo-3'
199 199 id_, params = build_data(
200 200 self.apikey, 'create_repo',
201 201 repo_name=repo_name,
202 202 repo_type=backend.alias)
203 203 response = api_call(self.app, params)
204 204
205 205 repo = RepoModel().get_by_repo_name(repo_name)
206 206 assert repo is not None
207 207 ret = {
208 208 'msg': 'Created new repository `%s`' % (repo_name,),
209 209 'success': True,
210 210 'task': None,
211 211 }
212 212 expected = ret
213 213 assert_ok(id_, expected, given=response.body)
214 214 fixture.destroy_repo(repo_name)
215 215
216 216 def test_api_create_repo_by_non_admin(self, backend):
217 217 repo_name = 'api-repo-4'
218 218 id_, params = build_data(
219 219 self.apikey_regular, 'create_repo',
220 220 repo_name=repo_name,
221 221 repo_type=backend.alias)
222 222 response = api_call(self.app, params)
223 223
224 224 repo = RepoModel().get_by_repo_name(repo_name)
225 225 assert repo is not None
226 226 ret = {
227 227 'msg': 'Created new repository `%s`' % (repo_name,),
228 228 'success': True,
229 229 'task': None,
230 230 }
231 231 expected = ret
232 232 assert_ok(id_, expected, given=response.body)
233 233 fixture.destroy_repo(repo_name)
234 234
235 235 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
236 236 repo_name = 'api-repo-5'
237 237 owner = 'i-dont-exist'
238 238 id_, params = build_data(
239 239 self.apikey_regular, 'create_repo',
240 240 repo_name=repo_name,
241 241 repo_type=backend.alias,
242 242 owner=owner)
243 243 response = api_call(self.app, params)
244 244
245 245 expected = 'Only RhodeCode super-admin can specify `owner` param'
246 246 assert_error(id_, expected, given=response.body)
247 247 fixture.destroy_repo(repo_name)
248 248
249 249 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
250 250 repo_group_name = 'no-access'
251 251 fixture.create_repo_group(repo_group_name)
252 252 repo_name = 'no-access/api-repo'
253 253
254 254 id_, params = build_data(
255 255 self.apikey_regular, 'create_repo',
256 256 repo_name=repo_name,
257 257 repo_type=backend.alias)
258 258 response = api_call(self.app, params)
259 259
260 260 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
261 261 repo_group_name)}
262 262 assert_error(id_, expected, given=response.body)
263 263 fixture.destroy_repo_group(repo_group_name)
264 264 fixture.destroy_repo(repo_name)
265 265
266 266 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
267 267 self, backend, user_util):
268 268
269 269 regular_user = user_util.create_user()
270 270 regular_user_api_key = regular_user.api_key
271 271
272 272 usr = UserModel().get_by_username(regular_user.username)
273 273 usr.inherit_default_permissions = False
274 274 Session().add(usr)
275 275
276 276 repo_name = backend.new_repo_name()
277 277 id_, params = build_data(
278 278 regular_user_api_key, 'create_repo',
279 279 repo_name=repo_name,
280 280 repo_type=backend.alias)
281 281 response = api_call(self.app, params)
282 282 expected = {
283 283 "repo_name": "You do not have the permission to "
284 284 "store repositories in the root location."}
285 285 assert_error(id_, expected, given=response.body)
286 286
287 287 def test_api_create_repo_exists(self, backend):
288 288 repo_name = backend.repo_name
289 289 id_, params = build_data(
290 290 self.apikey, 'create_repo',
291 291 repo_name=repo_name,
292 292 owner=TEST_USER_ADMIN_LOGIN,
293 293 repo_type=backend.alias,)
294 294 response = api_call(self.app, params)
295 295 expected = {
296 296 'unique_repo_name': 'Repository with name `{}` already exists'.format(
297 297 repo_name)}
298 298 assert_error(id_, expected, given=response.body)
299 299
300 300 @mock.patch.object(RepoModel, 'create', crash)
301 301 def test_api_create_repo_exception_occurred(self, backend):
302 302 repo_name = 'api-repo-6'
303 303 id_, params = build_data(
304 304 self.apikey, 'create_repo',
305 305 repo_name=repo_name,
306 306 owner=TEST_USER_ADMIN_LOGIN,
307 307 repo_type=backend.alias,)
308 308 response = api_call(self.app, params)
309 309 expected = 'failed to create repository `%s`' % (repo_name,)
310 310 assert_error(id_, expected, given=response.body)
311 311
312 312 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
313 313 (None, 'foo bar x', 'foo-bar-x'),
314 314 ('foo', '/foo//bar x', 'foo/bar-x'),
315 315 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
316 316 ])
317 317 def test_create_repo_with_extra_slashes_in_name(
318 318 self, backend, parent_group, dirty_name, expected_name):
319 319
320 320 if parent_group:
321 321 gr = fixture.create_repo_group(parent_group)
322 322 assert gr.group_name == parent_group
323 323
324 324 id_, params = build_data(
325 325 self.apikey, 'create_repo',
326 326 repo_name=dirty_name,
327 327 repo_type=backend.alias,
328 328 owner=TEST_USER_ADMIN_LOGIN,)
329 329 response = api_call(self.app, params)
330 330 expected ={
331 331 "msg": "Created new repository `{}`".format(expected_name),
332 332 "task": None,
333 333 "success": True
334 334 }
335 335 assert_ok(id_, expected, response.body)
336 336
337 337 repo = RepoModel().get_by_repo_name(expected_name)
338 338 assert repo is not None
339 339
340 340 expected = {
341 341 'msg': 'Created new repository `%s`' % (expected_name,),
342 342 'success': True,
343 343 'task': None,
344 344 }
345 345 assert_ok(id_, expected, given=response.body)
346 346 fixture.destroy_repo(expected_name)
347 347 if parent_group:
348 348 fixture.destroy_repo_group(parent_group)
@@ -1,288 +1,288 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_ok, assert_error, crash)
29 29 from rhodecode.tests.fixture import Fixture
30 30
31 31
32 32 fixture = Fixture()
33 33
34 34
35 35 @pytest.mark.usefixtures("testuser_api", "app")
36 36 class TestCreateRepoGroup(object):
37 37 def test_api_create_repo_group(self):
38 38 repo_group_name = 'api-repo-group'
39 39
40 40 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
41 41 assert repo_group is None
42 42
43 43 id_, params = build_data(
44 44 self.apikey, 'create_repo_group',
45 45 group_name=repo_group_name,
46 46 owner=TEST_USER_ADMIN_LOGIN,)
47 47 response = api_call(self.app, params)
48 48
49 49 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
50 50 assert repo_group is not None
51 51 ret = {
52 52 'msg': 'Created new repo group `%s`' % (repo_group_name,),
53 53 'repo_group': repo_group.get_api_data()
54 54 }
55 55 expected = ret
56 56 try:
57 57 assert_ok(id_, expected, given=response.body)
58 58 finally:
59 59 fixture.destroy_repo_group(repo_group_name)
60 60
61 61 def test_api_create_repo_group_in_another_group(self):
62 62 repo_group_name = 'api-repo-group'
63 63
64 64 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
65 65 assert repo_group is None
66 66 # create the parent
67 67 fixture.create_repo_group(repo_group_name)
68 68
69 69 full_repo_group_name = repo_group_name+'/'+repo_group_name
70 70 id_, params = build_data(
71 71 self.apikey, 'create_repo_group',
72 72 group_name=full_repo_group_name,
73 73 owner=TEST_USER_ADMIN_LOGIN,
74 74 copy_permissions=True)
75 75 response = api_call(self.app, params)
76 76
77 77 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
78 78 assert repo_group is not None
79 79 ret = {
80 80 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
81 81 'repo_group': repo_group.get_api_data()
82 82 }
83 83 expected = ret
84 84 try:
85 85 assert_ok(id_, expected, given=response.body)
86 86 finally:
87 87 fixture.destroy_repo_group(full_repo_group_name)
88 88 fixture.destroy_repo_group(repo_group_name)
89 89
90 90 def test_api_create_repo_group_in_another_group_not_existing(self):
91 91 repo_group_name = 'api-repo-group-no'
92 92
93 93 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
94 94 assert repo_group is None
95 95
96 96 full_repo_group_name = repo_group_name+'/'+repo_group_name
97 97 id_, params = build_data(
98 98 self.apikey, 'create_repo_group',
99 99 group_name=full_repo_group_name,
100 100 owner=TEST_USER_ADMIN_LOGIN,
101 101 copy_permissions=True)
102 102 response = api_call(self.app, params)
103 103 expected = {
104 104 'repo_group':
105 105 'Parent repository group `{}` does not exist'.format(
106 106 repo_group_name)}
107 107 assert_error(id_, expected, given=response.body)
108 108
109 109 def test_api_create_repo_group_that_exists(self):
110 110 repo_group_name = 'api-repo-group'
111 111
112 112 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
113 113 assert repo_group is None
114 114
115 115 fixture.create_repo_group(repo_group_name)
116 116 id_, params = build_data(
117 117 self.apikey, 'create_repo_group',
118 118 group_name=repo_group_name,
119 119 owner=TEST_USER_ADMIN_LOGIN,)
120 120 response = api_call(self.app, params)
121 121 expected = {
122 122 'unique_repo_group_name':
123 123 'Repository group with name `{}` already exists'.format(
124 124 repo_group_name)}
125 125 try:
126 126 assert_error(id_, expected, given=response.body)
127 127 finally:
128 128 fixture.destroy_repo_group(repo_group_name)
129 129
130 130 def test_api_create_repo_group_regular_user_wit_root_location_perms(
131 131 self, user_util):
132 132 regular_user = user_util.create_user()
133 133 regular_user_api_key = regular_user.api_key
134 134
135 135 repo_group_name = 'api-repo-group-by-regular-user'
136 136
137 137 usr = UserModel().get_by_username(regular_user.username)
138 138 usr.inherit_default_permissions = False
139 139 Session().add(usr)
140 140
141 141 UserModel().grant_perm(
142 142 regular_user.username, 'hg.repogroup.create.true')
143 143 Session().commit()
144 144
145 145 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
146 146 assert repo_group is None
147 147
148 148 id_, params = build_data(
149 149 regular_user_api_key, 'create_repo_group',
150 150 group_name=repo_group_name)
151 151 response = api_call(self.app, params)
152 152
153 153 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
154 154 assert repo_group is not None
155 155 expected = {
156 156 'msg': 'Created new repo group `%s`' % (repo_group_name,),
157 157 'repo_group': repo_group.get_api_data()
158 158 }
159 159 try:
160 160 assert_ok(id_, expected, given=response.body)
161 161 finally:
162 162 fixture.destroy_repo_group(repo_group_name)
163 163
164 164 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
165 165 self, user_util):
166 166
167 167 repo_group_name = 'api-repo-group-parent'
168 168
169 169 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
170 170 assert repo_group is None
171 171 # create the parent
172 172 fixture.create_repo_group(repo_group_name)
173 173
174 174 # user perms
175 175 regular_user = user_util.create_user()
176 176 regular_user_api_key = regular_user.api_key
177 177
178 178 usr = UserModel().get_by_username(regular_user.username)
179 179 usr.inherit_default_permissions = False
180 180 Session().add(usr)
181 181
182 182 RepoGroupModel().grant_user_permission(
183 183 repo_group_name, regular_user.username, 'group.admin')
184 184 Session().commit()
185 185
186 186 full_repo_group_name = repo_group_name + '/' + repo_group_name
187 187 id_, params = build_data(
188 188 regular_user_api_key, 'create_repo_group',
189 189 group_name=full_repo_group_name)
190 190 response = api_call(self.app, params)
191 191
192 192 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
193 193 assert repo_group is not None
194 194 expected = {
195 195 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
196 196 'repo_group': repo_group.get_api_data()
197 197 }
198 198 try:
199 199 assert_ok(id_, expected, given=response.body)
200 200 finally:
201 201 fixture.destroy_repo_group(full_repo_group_name)
202 202 fixture.destroy_repo_group(repo_group_name)
203 203
204 204 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
205 205 repo_group_name = 'api-repo-group'
206 206
207 207 id_, params = build_data(
208 208 self.apikey_regular, 'create_repo_group',
209 209 group_name=repo_group_name)
210 210 response = api_call(self.app, params)
211 211
212 212 expected = {
213 213 'repo_group':
214 214 u'You do not have the permission to store '
215 215 u'repository groups in the root location.'}
216 216 assert_error(id_, expected, given=response.body)
217 217
218 218 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
219 219 repo_group_name = 'api-repo-group-regular-user'
220 220
221 221 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
222 222 assert repo_group is None
223 223 # create the parent
224 224 fixture.create_repo_group(repo_group_name)
225 225
226 226 full_repo_group_name = repo_group_name+'/'+repo_group_name
227 227
228 228 id_, params = build_data(
229 229 self.apikey_regular, 'create_repo_group',
230 230 group_name=full_repo_group_name)
231 231 response = api_call(self.app, params)
232 232
233 233 expected = {
234 234 'repo_group':
235 235 u"You do not have the permissions to store "
236 236 u"repository groups inside repository group `{}`".format(repo_group_name)}
237 237 try:
238 238 assert_error(id_, expected, given=response.body)
239 239 finally:
240 240 fixture.destroy_repo_group(repo_group_name)
241 241
242 242 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
243 243 self):
244 244 repo_group_name = 'api-repo-group'
245 245
246 246 id_, params = build_data(
247 247 self.apikey_regular, 'create_repo_group',
248 248 group_name=repo_group_name,
249 249 owner=TEST_USER_ADMIN_LOGIN,)
250 250 response = api_call(self.app, params)
251 251
252 252 expected = "Only RhodeCode super-admin can specify `owner` param"
253 253 assert_error(id_, expected, given=response.body)
254 254
255 255 @mock.patch.object(RepoGroupModel, 'create', crash)
256 256 def test_api_create_repo_group_exception_occurred(self):
257 257 repo_group_name = 'api-repo-group'
258 258
259 259 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
260 260 assert repo_group is None
261 261
262 262 id_, params = build_data(
263 263 self.apikey, 'create_repo_group',
264 264 group_name=repo_group_name,
265 265 owner=TEST_USER_ADMIN_LOGIN,)
266 266 response = api_call(self.app, params)
267 267 expected = 'failed to create repo group `%s`' % (repo_group_name,)
268 268 assert_error(id_, expected, given=response.body)
269 269
270 270 def test_create_group_with_extra_slashes_in_name(self, user_util):
271 271 existing_repo_group = user_util.create_repo_group()
272 272 dirty_group_name = '//{}//group2//'.format(
273 273 existing_repo_group.group_name)
274 274 cleaned_group_name = '{}/group2'.format(
275 275 existing_repo_group.group_name)
276 276
277 277 id_, params = build_data(
278 278 self.apikey, 'create_repo_group',
279 279 group_name=dirty_group_name,
280 280 owner=TEST_USER_ADMIN_LOGIN,)
281 281 response = api_call(self.app, params)
282 282 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
283 283 expected = {
284 284 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
285 285 'repo_group': repo_group.get_api_data()
286 286 }
287 287 assert_ok(id_, expected, given=response.body)
288 288 fixture.destroy_repo_group(cleaned_group_name)
@@ -1,206 +1,206 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.lib.auth import check_password
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.tests import (
26 26 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_ok, assert_error, jsonify, crash)
29 29 from rhodecode.tests.fixture import Fixture
30 30 from rhodecode.model.db import RepoGroup
31 31
32 32
33 33 # TODO: mikhail: remove fixture from here
34 34 fixture = Fixture()
35 35
36 36
37 37 @pytest.mark.usefixtures("testuser_api", "app")
38 38 class TestCreateUser(object):
39 39 def test_api_create_existing_user(self):
40 40 id_, params = build_data(
41 41 self.apikey, 'create_user',
42 42 username=TEST_USER_ADMIN_LOGIN,
43 43 email='test@foo.com',
44 44 password='trololo')
45 45 response = api_call(self.app, params)
46 46
47 47 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
48 48 assert_error(id_, expected, given=response.body)
49 49
50 50 def test_api_create_user_with_existing_email(self):
51 51 id_, params = build_data(
52 52 self.apikey, 'create_user',
53 53 username=TEST_USER_ADMIN_LOGIN + 'new',
54 54 email=TEST_USER_REGULAR_EMAIL,
55 55 password='trololo')
56 56 response = api_call(self.app, params)
57 57
58 58 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
59 59 assert_error(id_, expected, given=response.body)
60 60
61 61 def test_api_create_user_with_wrong_username(self):
62 62 bad_username = '<> HELLO WORLD <>'
63 63 id_, params = build_data(
64 64 self.apikey, 'create_user',
65 65 username=bad_username,
66 66 email='new@email.com',
67 67 password='trololo')
68 68 response = api_call(self.app, params)
69 69
70 70 expected = {'username':
71 71 "Username may only contain alphanumeric characters "
72 72 "underscores, periods or dashes and must begin with "
73 73 "alphanumeric character or underscore"}
74 74 assert_error(id_, expected, given=response.body)
75 75
76 76 def test_api_create_user(self):
77 77 username = 'test_new_api_user'
78 78 email = username + "@foo.com"
79 79
80 80 id_, params = build_data(
81 81 self.apikey, 'create_user',
82 82 username=username,
83 83 email=email,
84 84 description='CTO of Things',
85 85 password='example')
86 86 response = api_call(self.app, params)
87 87
88 88 usr = UserModel().get_by_username(username)
89 89 ret = {
90 90 'msg': 'created new user `%s`' % (username,),
91 91 'user': jsonify(usr.get_api_data(include_secrets=True)),
92 92 }
93 93 try:
94 94 expected = ret
95 95 assert check_password('example', usr.password)
96 96 assert_ok(id_, expected, given=response.body)
97 97 finally:
98 98 fixture.destroy_user(usr.user_id)
99 99
100 100 def test_api_create_user_without_password(self):
101 101 username = 'test_new_api_user_passwordless'
102 102 email = username + "@foo.com"
103 103
104 104 id_, params = build_data(
105 105 self.apikey, 'create_user',
106 106 username=username,
107 107 email=email)
108 108 response = api_call(self.app, params)
109 109
110 110 usr = UserModel().get_by_username(username)
111 111 ret = {
112 112 'msg': 'created new user `%s`' % (username,),
113 113 'user': jsonify(usr.get_api_data(include_secrets=True)),
114 114 }
115 115 try:
116 116 expected = ret
117 117 assert_ok(id_, expected, given=response.body)
118 118 finally:
119 119 fixture.destroy_user(usr.user_id)
120 120
121 121 def test_api_create_user_with_extern_name(self):
122 122 username = 'test_new_api_user_passwordless'
123 123 email = username + "@foo.com"
124 124
125 125 id_, params = build_data(
126 126 self.apikey, 'create_user',
127 127 username=username,
128 128 email=email, extern_name='rhodecode')
129 129 response = api_call(self.app, params)
130 130
131 131 usr = UserModel().get_by_username(username)
132 132 ret = {
133 133 'msg': 'created new user `%s`' % (username,),
134 134 'user': jsonify(usr.get_api_data(include_secrets=True)),
135 135 }
136 136 try:
137 137 expected = ret
138 138 assert_ok(id_, expected, given=response.body)
139 139 finally:
140 140 fixture.destroy_user(usr.user_id)
141 141
142 142 def test_api_create_user_with_password_change(self):
143 143 username = 'test_new_api_user_password_change'
144 144 email = username + "@foo.com"
145 145
146 146 id_, params = build_data(
147 147 self.apikey, 'create_user',
148 148 username=username,
149 149 email=email, extern_name='rhodecode',
150 150 force_password_change=True)
151 151 response = api_call(self.app, params)
152 152
153 153 usr = UserModel().get_by_username(username)
154 154 ret = {
155 155 'msg': 'created new user `%s`' % (username,),
156 156 'user': jsonify(usr.get_api_data(include_secrets=True)),
157 157 }
158 158 try:
159 159 expected = ret
160 160 assert_ok(id_, expected, given=response.body)
161 161 finally:
162 162 fixture.destroy_user(usr.user_id)
163 163
164 164 def test_api_create_user_with_personal_repo_group(self):
165 165 username = 'test_new_api_user_personal_group'
166 166 email = username + "@foo.com"
167 167
168 168 id_, params = build_data(
169 169 self.apikey, 'create_user',
170 170 username=username,
171 171 email=email, extern_name='rhodecode',
172 172 create_personal_repo_group=True)
173 173 response = api_call(self.app, params)
174 174
175 175 usr = UserModel().get_by_username(username)
176 176 ret = {
177 177 'msg': 'created new user `%s`' % (username,),
178 178 'user': jsonify(usr.get_api_data(include_secrets=True)),
179 179 }
180 180
181 181 personal_group = RepoGroup.get_by_group_name(username)
182 182 assert personal_group
183 183 assert personal_group.personal == True
184 184 assert personal_group.user.username == username
185 185
186 186 try:
187 187 expected = ret
188 188 assert_ok(id_, expected, given=response.body)
189 189 finally:
190 190 fixture.destroy_repo_group(username)
191 191 fixture.destroy_user(usr.user_id)
192 192
193 193 @mock.patch.object(UserModel, 'create_or_update', crash)
194 194 def test_api_create_user_when_exception_happened(self):
195 195
196 196 username = 'test_new_api_user'
197 197 email = username + "@foo.com"
198 198
199 199 id_, params = build_data(
200 200 self.apikey, 'create_user',
201 201 username=username,
202 202 email=email,
203 203 password='trololo')
204 204 response = api_call(self.app, params)
205 205 expected = 'failed to create user `%s`' % (username,)
206 206 assert_error(id_, expected, given=response.body)
@@ -1,126 +1,126 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.model.user_group import UserGroupModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 28 from rhodecode.tests.fixture import Fixture
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestCreateUserGroup(object):
33 33 fixture = Fixture()
34 34
35 35 def test_api_create_user_group(self):
36 36 group_name = 'some_new_group'
37 37 id_, params = build_data(
38 38 self.apikey, 'create_user_group', group_name=group_name)
39 39 response = api_call(self.app, params)
40 40
41 41 ret = {
42 42 'msg': 'created new user group `%s`' % (group_name,),
43 43 'user_group': jsonify(
44 44 UserGroupModel()
45 45 .get_by_name(group_name)
46 46 .get_api_data()
47 47 )
48 48 }
49 49 expected = ret
50 50 assert_ok(id_, expected, given=response.body)
51 51 self.fixture.destroy_user_group(group_name)
52 52
53 53 def test_api_create_user_group_regular_user(self):
54 54 group_name = 'some_new_group'
55 55
56 56 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 57 usr.inherit_default_permissions = False
58 58 Session().add(usr)
59 59 UserModel().grant_perm(
60 60 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
61 61 Session().commit()
62 62
63 63 id_, params = build_data(
64 64 self.apikey_regular, 'create_user_group', group_name=group_name)
65 65 response = api_call(self.app, params)
66 66
67 67 expected = {
68 68 'msg': 'created new user group `%s`' % (group_name,),
69 69 'user_group': jsonify(
70 70 UserGroupModel()
71 71 .get_by_name(group_name)
72 72 .get_api_data()
73 73 )
74 74 }
75 75 try:
76 76 assert_ok(id_, expected, given=response.body)
77 77 finally:
78 78 self.fixture.destroy_user_group(group_name)
79 79 UserModel().revoke_perm(
80 80 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
81 81 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
82 82 usr.inherit_default_permissions = True
83 83 Session().add(usr)
84 84 Session().commit()
85 85
86 86 def test_api_create_user_group_regular_user_no_permission(self):
87 87 group_name = 'some_new_group'
88 88 id_, params = build_data(
89 89 self.apikey_regular, 'create_user_group', group_name=group_name)
90 90 response = api_call(self.app, params)
91 91 expected = "Access was denied to this resource."
92 92 assert_error(id_, expected, given=response.body)
93 93
94 94 def test_api_create_user_group_that_exist(self, user_util):
95 95 group = user_util.create_user_group()
96 96 group_name = group.users_group_name
97 97
98 98 id_, params = build_data(
99 99 self.apikey, 'create_user_group', group_name=group_name)
100 100 response = api_call(self.app, params)
101 101
102 102 expected = "user group `%s` already exist" % (group_name,)
103 103 assert_error(id_, expected, given=response.body)
104 104
105 105 @mock.patch.object(UserGroupModel, 'create', crash)
106 106 def test_api_create_user_group_exception_occurred(self):
107 107 group_name = 'exception_happens'
108 108 id_, params = build_data(
109 109 self.apikey, 'create_user_group', group_name=group_name)
110 110 response = api_call(self.app, params)
111 111
112 112 expected = 'failed to create group `%s`' % (group_name,)
113 113 assert_error(id_, expected, given=response.body)
114 114
115 115 def test_api_create_user_group_with_wrong_name(self, user_util):
116 116
117 117 group_name = 'wrong NAME <>'
118 118 id_, params = build_data(
119 119 self.apikey, 'create_user_group', group_name=group_name)
120 120 response = api_call(self.app, params)
121 121
122 122 expected = {"user_group_name":
123 123 "Allowed in name are letters, numbers, and `-`, `_`, "
124 124 "`.` Name must start with a letter or number. "
125 125 "Got `{}`".format(group_name)}
126 126 assert_error(id_, expected, given=response.body)
@@ -1,60 +1,60 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.gist import GistModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApiDeleteGist(object):
30 30 def test_api_delete_gist(self, gist_util):
31 31 gist_id = gist_util.create_gist().gist_access_id
32 32 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
33 33 response = api_call(self.app, params)
34 34 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
35 35 assert_ok(id_, expected, given=response.body)
36 36
37 37 def test_api_delete_gist_regular_user(self, gist_util):
38 38 gist_id = gist_util.create_gist(
39 39 owner=self.TEST_USER_LOGIN).gist_access_id
40 40 id_, params = build_data(
41 41 self.apikey_regular, 'delete_gist', gistid=gist_id)
42 42 response = api_call(self.app, params)
43 43 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 def test_api_delete_gist_regular_user_no_permission(self, gist_util):
47 47 gist_id = gist_util.create_gist().gist_access_id
48 48 id_, params = build_data(
49 49 self.apikey_regular, 'delete_gist', gistid=gist_id)
50 50 response = api_call(self.app, params)
51 51 expected = 'gist `%s` does not exist' % (gist_id,)
52 52 assert_error(id_, expected, given=response.body)
53 53
54 54 @mock.patch.object(GistModel, 'delete', crash)
55 55 def test_api_delete_gist_exception_occurred(self, gist_util):
56 56 gist_id = gist_util.create_gist().gist_access_id
57 57 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
58 58 response = api_call(self.app, params)
59 59 expected = 'failed to delete gist ID:%s' % (gist_id,)
60 60 assert_error(id_, expected, given=response.body)
@@ -1,73 +1,73 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.repo import RepoModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApiDeleteRepo(object):
30 30 def test_api_delete_repo(self, backend):
31 31 repo = backend.create_repo()
32 32 repo_name = repo.repo_name
33 33 id_, params = build_data(
34 34 self.apikey, 'delete_repo', repoid=repo.repo_name, )
35 35 response = api_call(self.app, params)
36 36
37 37 expected = {
38 38 'msg': 'Deleted repository `%s`' % (repo_name,),
39 39 'success': True
40 40 }
41 41 assert_ok(id_, expected, given=response.body)
42 42
43 43 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
44 44 repo = backend.create_repo(cur_user=user_regular.username)
45 45 repo_name = repo.repo_name
46 46 id_, params = build_data(
47 47 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 48 response = api_call(self.app, params)
49 49
50 50 expected = {
51 51 'msg': 'Deleted repository `%s`' % (repo_name,),
52 52 'success': True
53 53 }
54 54 assert_ok(id_, expected, given=response.body)
55 55
56 56 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 57 repo = backend.create_repo()
58 58 repo_name = repo.repo_name
59 59 id_, params = build_data(
60 60 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
61 61 response = api_call(self.app, params)
62 62 expected = 'repository `%s` does not exist' % (repo_name)
63 63 assert_error(id_, expected, given=response.body)
64 64
65 65 def test_api_delete_repo_exception_occurred(self, backend):
66 66 repo = backend.create_repo()
67 67 repo_name = repo.repo_name
68 68 id_, params = build_data(
69 69 self.apikey, 'delete_repo', repoid=repo.repo_name, )
70 70 with mock.patch.object(RepoModel, 'delete', crash):
71 71 response = api_call(self.app, params)
72 72 expected = 'failed to delete repository `%s`' % (repo_name,)
73 73 assert_error(id_, expected, given=response.body)
@@ -1,85 +1,85 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.repo_group import RepoGroupModel
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiDeleteRepoGroup(object):
31 31 def test_api_delete_repo_group(self, user_util):
32 32 repo_group = user_util.create_repo_group(auto_cleanup=False)
33 33 repo_group_name = repo_group.group_name
34 34 repo_group_id = repo_group.group_id
35 35 id_, params = build_data(
36 36 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
37 37 response = api_call(self.app, params)
38 38
39 39 ret = {
40 40 'msg': 'deleted repo group ID:%s %s' % (
41 41 repo_group_id, repo_group_name
42 42 ),
43 43 'repo_group': None
44 44 }
45 45 expected = ret
46 46 assert_ok(id_, expected, given=response.body)
47 47 gr = RepoGroupModel()._get_repo_group(repo_group_name)
48 48 assert gr is None
49 49
50 50 def test_api_delete_repo_group_regular_user(self, user_util):
51 51 repo_group = user_util.create_repo_group(auto_cleanup=False)
52 52 repo_group_name = repo_group.group_name
53 53 repo_group_id = repo_group.group_id
54 54
55 55 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
56 56 user_util.grant_user_permission_to_repo_group(
57 57 repo_group, user, 'group.admin')
58 58
59 59 id_, params = build_data(
60 60 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
61 61 response = api_call(self.app, params)
62 62
63 63 ret = {
64 64 'msg': 'deleted repo group ID:%s %s' % (
65 65 repo_group_id, repo_group_name
66 66 ),
67 67 'repo_group': None
68 68 }
69 69 expected = ret
70 70 assert_ok(id_, expected, given=response.body)
71 71 gr = RepoGroupModel()._get_repo_group(repo_group_name)
72 72 assert gr is None
73 73
74 74 def test_api_delete_repo_group_regular_user_no_permission(self, user_util):
75 75 repo_group = user_util.create_repo_group()
76 76 repo_group_name = repo_group.group_name
77 77
78 78 id_, params = build_data(
79 79 self.apikey_regular, 'delete_repo_group',
80 80 repogroupid=repo_group_name, )
81 81 response = api_call(self.app, params)
82 82
83 83 expected = 'repository group `%s` does not exist' % (
84 84 repo_group_name,)
85 85 assert_error(id_, expected, given=response.body)
@@ -1,56 +1,56 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestDeleteUser(object):
31 31 def test_api_delete_user(self, user_util):
32 32 usr = user_util.create_user(auto_cleanup=False)
33 33
34 34 username = usr.username
35 35 usr_id = usr.user_id
36 36
37 37 id_, params = build_data(self.apikey, 'delete_user', userid=username)
38 38 response = api_call(self.app, params)
39 39
40 40 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
41 41 'user': None}
42 42 expected = ret
43 43 assert_ok(id_, expected, given=response.body)
44 44
45 45 @mock.patch.object(UserModel, 'delete', crash)
46 46 def test_api_delete_user_when_exception_happened(self, user_util):
47 47 usr = user_util.create_user()
48 48 username = usr.username
49 49
50 50 id_, params = build_data(
51 51 self.apikey, 'delete_user', userid=username, )
52 52 response = api_call(self.app, params)
53 53 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
54 54 usr.username)
55 55 expected = ret
56 56 assert_error(id_, expected, given=response.body)
@@ -1,105 +1,105 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.model.user_group import UserGroupModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestDeleteUserGroup(object):
32 32 def test_api_delete_user_group(self, user_util):
33 33 user_group = user_util.create_user_group(auto_cleanup=False)
34 34 group_name = user_group.users_group_name
35 35 group_id = user_group.users_group_id
36 36 id_, params = build_data(
37 37 self.apikey, 'delete_user_group', usergroupid=group_name)
38 38 response = api_call(self.app, params)
39 39
40 40 expected = {
41 41 'user_group': None,
42 42 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
43 43 }
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 def test_api_delete_user_group_regular_user(self, user_util):
47 47 ugroup = user_util.create_user_group(auto_cleanup=False)
48 48 group_name = ugroup.users_group_name
49 49 group_id = ugroup.users_group_id
50 50 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
51 51
52 52 user_util.grant_user_permission_to_user_group(
53 53 ugroup, user, 'usergroup.admin')
54 54
55 55 id_, params = build_data(
56 56 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
57 57 response = api_call(self.app, params)
58 58
59 59 expected = {
60 60 'user_group': None,
61 61 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
62 62 }
63 63 assert_ok(id_, expected, given=response.body)
64 64
65 65 def test_api_delete_user_group_regular_user_no_permission(self, user_util):
66 66 user_group = user_util.create_user_group()
67 67 group_name = user_group.users_group_name
68 68
69 69 id_, params = build_data(
70 70 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
71 71 response = api_call(self.app, params)
72 72
73 73 expected = 'user group `%s` does not exist' % (group_name)
74 74 assert_error(id_, expected, given=response.body)
75 75
76 76 def test_api_delete_user_group_that_is_assigned(self, backend, user_util):
77 77 ugroup = user_util.create_user_group()
78 78 group_name = ugroup.users_group_name
79 79 repo = backend.create_repo()
80 80
81 81 ugr_to_perm = user_util.grant_user_group_permission_to_repo(
82 82 repo, ugroup, 'repository.write')
83 83 msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository)
84 84
85 85 id_, params = build_data(
86 86 self.apikey, 'delete_user_group',
87 87 usergroupid=group_name)
88 88 response = api_call(self.app, params)
89 89
90 90 expected = msg
91 91 assert_error(id_, expected, given=response.body)
92 92
93 93 def test_api_delete_user_group_exception_occurred(self, user_util):
94 94 ugroup = user_util.create_user_group()
95 95 group_name = ugroup.users_group_name
96 96 group_id = ugroup.users_group_id
97 97 id_, params = build_data(
98 98 self.apikey, 'delete_user_group',
99 99 usergroupid=group_name)
100 100
101 101 with mock.patch.object(UserGroupModel, 'delete', crash):
102 102 response = api_call(self.app, params)
103 103 expected = 'failed to delete user group ID:%s %s' % (
104 104 group_id, group_name)
105 105 assert_error(id_, expected, given=response.body)
@@ -1,78 +1,78 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.api.views import deprecated_api
24 24 from rhodecode.lib.ext_json import json
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestCommitComment(object):
31 31 def test_deprecated_message_in_docstring(self):
32 32 docstring = deprecated_api.changeset_comment.__doc__
33 33 assert '.. deprecated:: 3.4.0' in docstring
34 34 assert 'Please use method `comment_commit` instead.' in docstring
35 35
36 36 def test_deprecated_message_in_retvalue(self):
37 37
38 38 id_, params = build_data(
39 39 self.apikey, 'show_ip')
40 40 response = api_call(self.app, params)
41 41
42 42 expected = {
43 43 'id': id_,
44 44 'error': None,
45 45 'result': json.loads(response.body)['result'],
46 46 'DEPRECATION_WARNING':
47 47 'DEPRECATED METHOD Please use method `get_ip` instead.'
48 48 }
49 49 assert expected == json.loads(response.body)
50 50
51 51 # def test_calls_comment_commit(self, backend, no_notifications):
52 52 # data = {
53 53 # 'repoid': backend.repo_name,
54 54 # 'status': ChangesetStatus.STATUS_APPROVED,
55 55 # 'message': 'Approved',
56 56 # 'revision': 'tip'
57 57 # }
58 58 # with patch.object(repo_api, 'changeset_commit') as comment_mock:
59 59 # id_, params = build_data(self.apikey, 'comment_commit', **data)
60 60 # api_call(self.app, params)
61 61 #
62 62 # _, call_args = comment_mock.call_args
63 63 # data['commit_id'] = data.pop('revision')
64 64 # for key in data:
65 65 # assert call_args[key] == data[key]
66 66
67 67 # def test_warning_log_contains_deprecation_message(self):
68 68 # api = self.SampleApi()
69 69 # with patch.object(utils, 'log') as log_mock:
70 70 # api.api_method()
71 71 #
72 72 # assert log_mock.warning.call_count == 1
73 73 # call_args = log_mock.warning.call_args[0]
74 74 # assert (
75 75 # call_args[0] ==
76 76 # 'DEPRECATED API CALL on function %s, please use `%s` instead')
77 77 # assert call_args[1].__name__ == 'api_method'
78 78 # assert call_args[2] == 'new_method' No newline at end of file
@@ -1,278 +1,278 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo import RepoModel
26 26 from rhodecode.model.repo_group import RepoGroupModel
27 27 from rhodecode.model.user import UserModel
28 28 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
29 29 from rhodecode.api.tests.utils import (
30 30 build_data, api_call, assert_error, assert_ok, crash)
31 31 from rhodecode.tests.fixture import Fixture
32 32
33 33
34 34 fixture = Fixture()
35 35
36 36
37 37 @pytest.mark.usefixtures("testuser_api", "app")
38 38 class TestApiForkRepo(object):
39 39 def test_api_fork_repo(self, backend):
40 40 source_name = backend['minimal'].repo_name
41 41 fork_name = backend.new_repo_name()
42 42
43 43 id_, params = build_data(
44 44 self.apikey, 'fork_repo',
45 45 repoid=source_name,
46 46 fork_name=fork_name,
47 47 owner=TEST_USER_ADMIN_LOGIN)
48 48 response = api_call(self.app, params)
49 49
50 50 expected = {
51 51 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
52 52 'success': True,
53 53 'task': None,
54 54 }
55 55 try:
56 56 assert_ok(id_, expected, given=response.body)
57 57 finally:
58 58 fixture.destroy_repo(fork_name)
59 59
60 60 def test_api_fork_repo_into_group(self, backend, user_util):
61 61 source_name = backend['minimal'].repo_name
62 62 repo_group = user_util.create_repo_group()
63 63 fork_name = '%s/api-repo-fork' % repo_group.group_name
64 64 id_, params = build_data(
65 65 self.apikey, 'fork_repo',
66 66 repoid=source_name,
67 67 fork_name=fork_name,
68 68 owner=TEST_USER_ADMIN_LOGIN)
69 69 response = api_call(self.app, params)
70 70
71 71 ret = {
72 72 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
73 73 'success': True,
74 74 'task': None,
75 75 }
76 76 expected = ret
77 77 try:
78 78 assert_ok(id_, expected, given=response.body)
79 79 finally:
80 80 fixture.destroy_repo(fork_name)
81 81
82 82 def test_api_fork_repo_non_admin(self, backend):
83 83 source_name = backend['minimal'].repo_name
84 84 fork_name = backend.new_repo_name()
85 85
86 86 id_, params = build_data(
87 87 self.apikey_regular, 'fork_repo',
88 88 repoid=source_name,
89 89 fork_name=fork_name)
90 90 response = api_call(self.app, params)
91 91
92 92 expected = {
93 93 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
94 94 'success': True,
95 95 'task': None,
96 96 }
97 97 try:
98 98 assert_ok(id_, expected, given=response.body)
99 99 finally:
100 100 fixture.destroy_repo(fork_name)
101 101
102 102 def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util):
103 103 source_name = backend['minimal'].repo_name
104 104 repo_group = user_util.create_repo_group()
105 105 repo_group_name = repo_group.group_name
106 106 fork_name = '%s/api-repo-fork' % repo_group_name
107 107
108 108 id_, params = build_data(
109 109 self.apikey_regular, 'fork_repo',
110 110 repoid=source_name,
111 111 fork_name=fork_name)
112 112 response = api_call(self.app, params)
113 113
114 114 expected = {
115 115 'repo_group': 'Repository group `{}` does not exist'.format(
116 116 repo_group_name)}
117 117 try:
118 118 assert_error(id_, expected, given=response.body)
119 119 finally:
120 120 fixture.destroy_repo(fork_name)
121 121
122 122 def test_api_fork_repo_non_admin_into_group(self, backend, user_util):
123 123 source_name = backend['minimal'].repo_name
124 124 repo_group = user_util.create_repo_group()
125 125 fork_name = '%s/api-repo-fork' % repo_group.group_name
126 126
127 127 RepoGroupModel().grant_user_permission(
128 128 repo_group, self.TEST_USER_LOGIN, 'group.admin')
129 129 Session().commit()
130 130
131 131 id_, params = build_data(
132 132 self.apikey_regular, 'fork_repo',
133 133 repoid=source_name,
134 134 fork_name=fork_name)
135 135 response = api_call(self.app, params)
136 136
137 137 expected = {
138 138 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
139 139 'success': True,
140 140 'task': None,
141 141 }
142 142 try:
143 143 assert_ok(id_, expected, given=response.body)
144 144 finally:
145 145 fixture.destroy_repo(fork_name)
146 146
147 147 def test_api_fork_repo_non_admin_specify_owner(self, backend):
148 148 source_name = backend['minimal'].repo_name
149 149 fork_name = backend.new_repo_name()
150 150 id_, params = build_data(
151 151 self.apikey_regular, 'fork_repo',
152 152 repoid=source_name,
153 153 fork_name=fork_name,
154 154 owner=TEST_USER_ADMIN_LOGIN)
155 155 response = api_call(self.app, params)
156 156 expected = 'Only RhodeCode super-admin can specify `owner` param'
157 157 assert_error(id_, expected, given=response.body)
158 158
159 159 def test_api_fork_repo_non_admin_no_permission_of_source_repo(
160 160 self, backend):
161 161 source_name = backend['minimal'].repo_name
162 162 RepoModel().grant_user_permission(repo=source_name,
163 163 user=self.TEST_USER_LOGIN,
164 164 perm='repository.none')
165 165 fork_name = backend.new_repo_name()
166 166 id_, params = build_data(
167 167 self.apikey_regular, 'fork_repo',
168 168 repoid=backend.repo_name,
169 169 fork_name=fork_name)
170 170 response = api_call(self.app, params)
171 171 expected = 'repository `%s` does not exist' % (backend.repo_name)
172 172 assert_error(id_, expected, given=response.body)
173 173
174 174 def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level(
175 175 self, backend, user_util):
176 176
177 177 regular_user = user_util.create_user()
178 178 regular_user_api_key = regular_user.api_key
179 179 usr = UserModel().get_by_username(regular_user.username)
180 180 usr.inherit_default_permissions = False
181 181 Session().add(usr)
182 182 UserModel().grant_perm(regular_user.username, 'hg.fork.repository')
183 183
184 184 source_name = backend['minimal'].repo_name
185 185 fork_name = backend.new_repo_name()
186 186 id_, params = build_data(
187 187 regular_user_api_key, 'fork_repo',
188 188 repoid=source_name,
189 189 fork_name=fork_name)
190 190 response = api_call(self.app, params)
191 191 expected = {
192 192 "repo_name": "You do not have the permission to "
193 193 "store repositories in the root location."}
194 194 assert_error(id_, expected, given=response.body)
195 195
196 196 def test_api_fork_repo_non_admin_no_permission_to_fork(
197 197 self, backend, user_util):
198 198
199 199 regular_user = user_util.create_user()
200 200 regular_user_api_key = regular_user.api_key
201 201 usr = UserModel().get_by_username(regular_user.username)
202 202 usr.inherit_default_permissions = False
203 203 Session().add(usr)
204 204
205 205 source_name = backend['minimal'].repo_name
206 206 fork_name = backend.new_repo_name()
207 207 id_, params = build_data(
208 208 regular_user_api_key, 'fork_repo',
209 209 repoid=source_name,
210 210 fork_name=fork_name)
211 211 response = api_call(self.app, params)
212 212
213 213 expected = "Access was denied to this resource."
214 214 assert_error(id_, expected, given=response.body)
215 215
216 216 def test_api_fork_repo_unknown_owner(self, backend):
217 217 source_name = backend['minimal'].repo_name
218 218 fork_name = backend.new_repo_name()
219 219 owner = 'i-dont-exist'
220 220 id_, params = build_data(
221 221 self.apikey, 'fork_repo',
222 222 repoid=source_name,
223 223 fork_name=fork_name,
224 224 owner=owner)
225 225 response = api_call(self.app, params)
226 226 expected = 'user `%s` does not exist' % (owner,)
227 227 assert_error(id_, expected, given=response.body)
228 228
229 229 def test_api_fork_repo_fork_exists(self, backend):
230 230 source_name = backend['minimal'].repo_name
231 231 fork_name = backend.new_repo_name()
232 232 fork_repo = fixture.create_fork(source_name, fork_name)
233 233
234 234 id_, params = build_data(
235 235 self.apikey, 'fork_repo',
236 236 repoid=source_name,
237 237 fork_name=fork_name,
238 238 owner=TEST_USER_ADMIN_LOGIN)
239 239 response = api_call(self.app, params)
240 240
241 241 try:
242 242 expected = {
243 243 'unique_repo_name': 'Repository with name `{}` already exists'.format(
244 244 fork_name)}
245 245 assert_error(id_, expected, given=response.body)
246 246 finally:
247 247 fixture.destroy_repo(fork_repo.repo_name)
248 248
249 249 def test_api_fork_repo_repo_exists(self, backend):
250 250 source_name = backend['minimal'].repo_name
251 251 fork_name = source_name
252 252
253 253 id_, params = build_data(
254 254 self.apikey, 'fork_repo',
255 255 repoid=source_name,
256 256 fork_name=fork_name,
257 257 owner=TEST_USER_ADMIN_LOGIN)
258 258 response = api_call(self.app, params)
259 259
260 260 expected = {
261 261 'unique_repo_name': 'Repository with name `{}` already exists'.format(
262 262 fork_name)}
263 263 assert_error(id_, expected, given=response.body)
264 264
265 265 @mock.patch.object(RepoModel, 'create_fork', crash)
266 266 def test_api_fork_repo_exception_occurred(self, backend):
267 267 source_name = backend['minimal'].repo_name
268 268 fork_name = backend.new_repo_name()
269 269 id_, params = build_data(
270 270 self.apikey, 'fork_repo',
271 271 repoid=source_name,
272 272 fork_name=fork_name,
273 273 owner=TEST_USER_ADMIN_LOGIN)
274 274 response = api_call(self.app, params)
275 275
276 276 expected = 'failed to fork repository `%s` as `%s`' % (source_name,
277 277 fork_name)
278 278 assert_error(id_, expected, given=response.body)
@@ -1,114 +1,114 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21 from rhodecode.tests import HG_REPO
22 22 from rhodecode.api.tests.utils import (
23 23 build_data, api_call, assert_error, assert_ok)
24 24
25 25
26 26 @pytest.mark.usefixtures("testuser_api", "app")
27 27 class TestApiSearch(object):
28 28
29 29 @pytest.mark.parametrize("sort_dir", [
30 30 "asc",
31 31 "desc",
32 32 ])
33 33 @pytest.mark.parametrize("sort", [
34 34 "xxx",
35 35 "author_email",
36 36 "date",
37 37 "message",
38 38 ])
39 39 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
40 40 ('todo', 23, [
41 41 'vcs/backends/hg/inmemory.py',
42 42 'vcs/tests/test_git.py']),
43 43 ('extension:rst installation', 6, [
44 44 'docs/index.rst',
45 45 'docs/installation.rst']),
46 46 ('def repo', 87, [
47 47 'vcs/tests/test_git.py',
48 48 'vcs/tests/test_changesets.py']),
49 49 ('repository:%s def test' % HG_REPO, 18, [
50 50 'vcs/tests/test_git.py',
51 51 'vcs/tests/test_changesets.py']),
52 52 ('"def main"', 9, [
53 53 'vcs/__init__.py',
54 54 'vcs/tests/__init__.py',
55 55 'vcs/utils/progressbar.py']),
56 56 ('owner:test_admin', 358, [
57 57 'vcs/tests/base.py',
58 58 'MANIFEST.in',
59 59 'vcs/utils/termcolors.py',
60 60 'docs/theme/ADC/static/documentation.png']),
61 61 ('owner:test_admin def main', 72, [
62 62 'vcs/__init__.py',
63 63 'vcs/tests/test_utils_filesize.py',
64 64 'vcs/tests/test_cli.py']),
65 65 ('owner:michał test', 0, []),
66 66 ])
67 67 def test_search_content_results(self, sort_dir, sort, query, expected_hits, expected_paths):
68 68 id_, params = build_data(
69 69 self.apikey_regular, 'search',
70 70 search_query=query,
71 71 search_sort='{}:{}'.format(sort_dir, sort),
72 72 search_type='content')
73 73
74 74 response = api_call(self.app, params)
75 75 json_response = response.json
76 76
77 77 assert json_response['result']['item_count'] == expected_hits
78 78 paths = [x['f_path'] for x in json_response['result']['results']]
79 79
80 80 for expected_path in expected_paths:
81 81 assert expected_path in paths
82 82
83 83 @pytest.mark.parametrize("sort_dir", [
84 84 "asc",
85 85 "desc",
86 86 ])
87 87 @pytest.mark.parametrize("sort", [
88 88 "xxx",
89 89 "date",
90 90 "file",
91 91 "size",
92 92 ])
93 93 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
94 94 ('readme.rst', 3, []),
95 95 ('test*', 75, []),
96 96 ('*model*', 1, []),
97 97 ('extension:rst', 48, []),
98 98 ('extension:rst api', 24, []),
99 99 ])
100 100 def test_search_file_paths(self, sort_dir, sort, query, expected_hits, expected_paths):
101 101 id_, params = build_data(
102 102 self.apikey_regular, 'search',
103 103 search_query=query,
104 104 search_sort='{}:{}'.format(sort_dir, sort),
105 105 search_type='path')
106 106
107 107 response = api_call(self.app, params)
108 108 json_response = response.json
109 109
110 110 assert json_response['result']['item_count'] == expected_hits
111 111 paths = [x['f_path'] for x in json_response['result']['results']]
112 112
113 113 for expected_path in expected_paths:
114 114 assert expected_path in paths
@@ -1,101 +1,101 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.lib.str_utils import safe_bytes
24 24 from rhodecode.model.db import Gist
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiGetGist(object):
31 31 def test_api_get_gist(self, gist_util, http_host_only_stub):
32 32 gist = gist_util.create_gist()
33 33 gist_id = gist.gist_access_id
34 34 gist_created_on = gist.created_on
35 35 gist_modified_at = gist.modified_at
36 36 id_, params = build_data(
37 37 self.apikey, 'get_gist', gistid=gist_id, )
38 38 response = api_call(self.app, params)
39 39
40 40 expected = {
41 41 'access_id': gist_id,
42 42 'created_on': gist_created_on,
43 43 'modified_at': gist_modified_at,
44 44 'description': 'new-gist',
45 45 'expires': -1.0,
46 46 'gist_id': int(gist_id),
47 47 'type': 'public',
48 48 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
49 49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 50 'content': None,
51 51 }
52 52
53 53 assert_ok(id_, expected, given=response.body)
54 54
55 55 def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
56 56 mapping = {
57 57 b'filename1.txt': {'content': b'hello world'},
58 58 safe_bytes('filename1ą.txt'): {'content': safe_bytes('hello worldę')}
59 59 }
60 60 gist = gist_util.create_gist(gist_mapping=mapping)
61 61 gist_id = gist.gist_access_id
62 62 gist_created_on = gist.created_on
63 63 gist_modified_at = gist.modified_at
64 64 id_, params = build_data(
65 65 self.apikey, 'get_gist', gistid=gist_id, content=True)
66 66 response = api_call(self.app, params)
67 67
68 68 expected = {
69 69 'access_id': gist_id,
70 70 'created_on': gist_created_on,
71 71 'modified_at': gist_modified_at,
72 72 'description': 'new-gist',
73 73 'expires': -1.0,
74 74 'gist_id': int(gist_id),
75 75 'type': 'public',
76 76 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
77 77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 78 'content': {
79 79 u'filename1.txt': u'hello world',
80 80 u'filename1ą.txt': u'hello worldę'
81 81 },
82 82 }
83 83
84 84 assert_ok(id_, expected, given=response.body)
85 85
86 86 def test_api_get_gist_not_existing(self):
87 87 id_, params = build_data(
88 88 self.apikey_regular, 'get_gist', gistid='12345', )
89 89 response = api_call(self.app, params)
90 90 expected = 'gist `%s` does not exist' % ('12345',)
91 91 assert_error(id_, expected, given=response.body)
92 92
93 93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
94 94 gist = gist_util.create_gist()
95 95 gist_id = gist.gist_access_id
96 96 id_, params = build_data(
97 97 self.apikey_regular, 'get_gist', gistid=gist_id, )
98 98 response = api_call(self.app, params)
99 99
100 100 expected = 'gist `%s` does not exist' % (gist_id,)
101 101 assert_error(id_, expected, given=response.body)
@@ -1,73 +1,73 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApiGetGist(object):
30 30 def test_api_get_gists(self, gist_util):
31 31 gist_util.create_gist()
32 32 gist_util.create_gist()
33 33
34 34 id_, params = build_data(self.apikey, 'get_gists')
35 35 response = api_call(self.app, params)
36 36 assert len(response.json['result']) == 2
37 37
38 38 def test_api_get_gists_regular_user(self, gist_util):
39 39 # by admin
40 40 gist_util.create_gist()
41 41 gist_util.create_gist()
42 42
43 43 # by reg user
44 44 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
45 45 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
46 46 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
47 47
48 48 id_, params = build_data(self.apikey_regular, 'get_gists')
49 49 response = api_call(self.app, params)
50 50 assert len(response.json['result']) == 3
51 51
52 52 def test_api_get_gists_only_for_regular_user(self, gist_util):
53 53 # by admin
54 54 gist_util.create_gist()
55 55 gist_util.create_gist()
56 56
57 57 # by reg user
58 58 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
59 59 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
60 60 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
61 61
62 62 id_, params = build_data(
63 63 self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN)
64 64 response = api_call(self.app, params)
65 65 assert len(response.json['result']) == 3
66 66
67 67 def test_api_get_gists_regular_user_with_different_userid(self):
68 68 id_, params = build_data(
69 69 self.apikey_regular, 'get_gists',
70 70 userid=TEST_USER_ADMIN_LOGIN)
71 71 response = api_call(self.app, params)
72 72 expected = 'userid is not the same as your user'
73 73 assert_error(id_, expected, given=response.body)
@@ -1,35 +1,35 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 24
25 25
26 26 @pytest.mark.usefixtures("testuser_api", "app")
27 27 class TestGetIp(object):
28 28 def test_api_get_ip(self):
29 29 id_, params = build_data(self.apikey, 'get_ip')
30 30 response = api_call(self.app, params)
31 31 expected = {
32 32 'server_ip_addr': '0.0.0.0',
33 33 'user_ips': []
34 34 }
35 35 assert_ok(id_, expected, given=response.body)
@@ -1,90 +1,90 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import Repository, User
24 24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGetLocks(object):
31 31 def test_api_get_user_locks_regular_user(self):
32 32 id_, params = build_data(self.apikey_regular, 'get_user_locks')
33 33 response = api_call(self.app, params)
34 34 expected = []
35 35 assert_ok(id_, expected, given=response.body)
36 36
37 37 def test_api_get_user_locks_with_userid_regular_user(self):
38 38 id_, params = build_data(
39 39 self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN)
40 40 response = api_call(self.app, params)
41 41 expected = 'userid is not the same as your user'
42 42 assert_error(id_, expected, given=response.body)
43 43
44 44 def test_api_get_user_locks(self):
45 45 id_, params = build_data(self.apikey, 'get_user_locks')
46 46 response = api_call(self.app, params)
47 47 expected = []
48 48 assert_ok(id_, expected, given=response.body)
49 49
50 50 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
51 51 ('apikey', True),
52 52 ('apikey_regular', False),
53 53 ])
54 54 def test_api_get_user_locks_with_one_locked_repo(
55 55 self, apikey_attr, expect_secrets, backend):
56 56
57 57 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
58 58 Repository.lock(
59 59 repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
60 60
61 61 apikey = getattr(self, apikey_attr)
62 62
63 63 id_, params = build_data(apikey, 'get_user_locks')
64 64 if apikey_attr == 'apikey':
65 65 # super-admin should call in specific user
66 66 id_, params = build_data(apikey, 'get_user_locks',
67 67 userid=self.TEST_USER_LOGIN)
68 68
69 69 response = api_call(self.app, params)
70 70 expected = [repo.get_api_data(include_secrets=expect_secrets)]
71 71 assert_ok(id_, expected, given=response.body)
72 72
73 73 def test_api_get_user_locks_with_one_locked_repo_for_specific_user(
74 74 self, backend):
75 75 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
76 76
77 77 Repository.lock(repo, User.get_by_username(
78 78 self.TEST_USER_LOGIN).user_id)
79 79 id_, params = build_data(
80 80 self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN)
81 81 response = api_call(self.app, params)
82 82 expected = [repo.get_api_data(include_secrets=True)]
83 83 assert_ok(id_, expected, given=response.body)
84 84
85 85 def test_api_get_user_locks_with_userid(self):
86 86 id_, params = build_data(
87 87 self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN)
88 88 response = api_call(self.app, params)
89 89 expected = []
90 90 assert_ok(id_, expected, given=response.body)
@@ -1,62 +1,62 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 24
25 25
26 26 @pytest.mark.usefixtures("testuser_api", "app")
27 27 class TestGetMethod(object):
28 28 def test_get_methods_no_matches(self):
29 29 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
30 30 response = api_call(self.app, params)
31 31
32 32 expected = []
33 33 assert_ok(id_, expected, given=response.body)
34 34
35 35 def test_get_methods(self):
36 36 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
37 37 response = api_call(self.app, params)
38 38
39 39 expected = [
40 40 'changeset_comment', 'comment_pull_request', 'get_pull_request_comments',
41 41 'comment_commit', 'edit_comment', 'get_comment', 'get_repo_comments'
42 42 ]
43 43 assert_ok(id_, expected, given=response.body)
44 44
45 45 def test_get_methods_on_single_match(self):
46 46 id_, params = build_data(self.apikey, 'get_method',
47 47 pattern='*comment_commit*')
48 48 response = api_call(self.app, params)
49 49
50 50 expected = ['comment_commit',
51 51 {'apiuser': '<RequiredType>',
52 52 'comment_type': "<Optional:'note'>",
53 53 'commit_id': '<RequiredType>',
54 54 'extra_recipients': '<Optional:[]>',
55 55 'message': '<RequiredType>',
56 56 'repoid': '<RequiredType>',
57 57 'request': '<RequiredType>',
58 58 'resolves_comment_id': '<Optional:None>',
59 59 'status': '<Optional:None>',
60 60 'userid': '<Optional:<OptionalAttr:apiuser>>',
61 61 'send_email': '<Optional:True>'}]
62 62 assert_ok(id_, expected, given=response.body)
@@ -1,143 +1,143 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22 import urlobject
23 23
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok)
26 26 from rhodecode.lib import helpers as h
27 27 from rhodecode.lib.str_utils import safe_str
28 28
29 29
30 30 pytestmark = pytest.mark.backends("git", "hg")
31 31
32 32
33 33 @pytest.mark.usefixtures("testuser_api", "app")
34 34 class TestGetPullRequest(object):
35 35
36 36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
37 37 from rhodecode.model.pull_request import PullRequestModel
38 38 pull_request = pr_util.create_pull_request(mergeable=True)
39 39 id_, params = build_data(
40 40 self.apikey, 'get_pull_request',
41 41 pullrequestid=pull_request.pull_request_id, merge_state=True)
42 42
43 43 response = api_call(self.app, params)
44 44
45 45 assert response.status == '200 OK'
46 46
47 47 url_obj = urlobject.URLObject(
48 48 h.route_url(
49 49 'pullrequest_show',
50 50 repo_name=pull_request.target_repo.repo_name,
51 51 pull_request_id=pull_request.pull_request_id))
52 52
53 53 pr_url = safe_str(
54 54 url_obj.with_netloc(http_host_only_stub))
55 55 source_url = safe_str(
56 56 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
57 57 target_url = safe_str(
58 58 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
59 59 shadow_url = safe_str(
60 60 PullRequestModel().get_shadow_clone_url(pull_request))
61 61
62 62 expected = {
63 63 'pull_request_id': pull_request.pull_request_id,
64 64 'url': pr_url,
65 65 'title': pull_request.title,
66 66 'description': pull_request.description,
67 67 'status': pull_request.status,
68 68 'state': pull_request.pull_request_state,
69 69 'created_on': pull_request.created_on,
70 70 'updated_on': pull_request.updated_on,
71 71 'commit_ids': pull_request.revisions,
72 72 'review_status': pull_request.calculated_review_status(),
73 73 'mergeable': {
74 74 'status': True,
75 75 'message': 'This pull request can be automatically merged.',
76 76 },
77 77 'source': {
78 78 'clone_url': source_url,
79 79 'repository': pull_request.source_repo.repo_name,
80 80 'reference': {
81 81 'name': pull_request.source_ref_parts.name,
82 82 'type': pull_request.source_ref_parts.type,
83 83 'commit_id': pull_request.source_ref_parts.commit_id,
84 84 },
85 85 },
86 86 'target': {
87 87 'clone_url': target_url,
88 88 'repository': pull_request.target_repo.repo_name,
89 89 'reference': {
90 90 'name': pull_request.target_ref_parts.name,
91 91 'type': pull_request.target_ref_parts.type,
92 92 'commit_id': pull_request.target_ref_parts.commit_id,
93 93 },
94 94 },
95 95 'merge': {
96 96 'clone_url': shadow_url,
97 97 'reference': {
98 98 'name': pull_request.shadow_merge_ref.name,
99 99 'type': pull_request.shadow_merge_ref.type,
100 100 'commit_id': pull_request.shadow_merge_ref.commit_id,
101 101 },
102 102 },
103 103 'author': pull_request.author.get_api_data(include_secrets=False,
104 104 details='basic'),
105 105 'reviewers': [
106 106 {
107 107 'user': reviewer.get_api_data(include_secrets=False,
108 108 details='basic'),
109 109 'reasons': reasons,
110 110 'review_status': st[0][1].status if st else 'not_reviewed',
111 111 }
112 112 for obj, reviewer, reasons, mandatory, st in
113 113 pull_request.reviewers_statuses()
114 114 ]
115 115 }
116 116 assert_ok(id_, expected, response.body)
117 117
118 118 def test_api_get_pull_request_repo_error(self, pr_util):
119 119 pull_request = pr_util.create_pull_request()
120 120 id_, params = build_data(
121 121 self.apikey, 'get_pull_request',
122 122 repoid=666, pullrequestid=pull_request.pull_request_id)
123 123 response = api_call(self.app, params)
124 124
125 125 expected = 'repository `666` does not exist'
126 126 assert_error(id_, expected, given=response.body)
127 127
128 128 def test_api_get_pull_request_pull_request_error(self):
129 129 id_, params = build_data(
130 130 self.apikey, 'get_pull_request', pullrequestid=666)
131 131 response = api_call(self.app, params)
132 132
133 133 expected = 'pull request `666` does not exist'
134 134 assert_error(id_, expected, given=response.body)
135 135
136 136 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
137 137 id_, params = build_data(
138 138 self.apikey, 'get_pull_request',
139 139 pullrequestid=666)
140 140 response = api_call(self.app, params)
141 141
142 142 expected = 'pull request `666` does not exist'
143 143 assert_error(id_, expected, given=response.body)
@@ -1,83 +1,83 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.api.tests.utils import (
24 24 build_data, api_call, assert_error, assert_ok)
25 25
26 26 pytestmark = pytest.mark.backends("git", "hg")
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGetPullRequestComments(object):
31 31
32 32 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
33 33 from rhodecode.model.pull_request import PullRequestModel
34 34
35 35 pull_request = pr_util.create_pull_request(mergeable=True)
36 36 id_, params = build_data(
37 37 self.apikey, 'get_pull_request_comments',
38 38 pullrequestid=pull_request.pull_request_id)
39 39
40 40 response = api_call(self.app, params)
41 41
42 42 assert response.status == '200 OK'
43 43 resp_date = response.json['result'][0]['comment_created_on']
44 44 resp_comment_id = response.json['result'][0]['comment_id']
45 45
46 46 expected = [
47 47 {'comment_author': {'active': True,
48 48 'full_name_or_username': 'RhodeCode Admin',
49 49 'username': 'test_admin'},
50 50 'comment_created_on': resp_date,
51 51 'comment_f_path': None,
52 52 'comment_id': resp_comment_id,
53 53 'comment_lineno': None,
54 54 'comment_status': {'status': 'under_review',
55 55 'status_lbl': 'Under Review'},
56 56 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
57 57 'comment_type': 'note',
58 58 'comment_resolved_by': None,
59 59 'pull_request_version': None,
60 60 'comment_last_version': 0,
61 61 'comment_commit_id': None,
62 62 'comment_pull_request_id': pull_request.pull_request_id
63 63 }
64 64 ]
65 65 assert_ok(id_, expected, response.body)
66 66
67 67 def test_api_get_pull_request_comments_repo_error(self, pr_util):
68 68 pull_request = pr_util.create_pull_request()
69 69 id_, params = build_data(
70 70 self.apikey, 'get_pull_request_comments',
71 71 repoid=666, pullrequestid=pull_request.pull_request_id)
72 72 response = api_call(self.app, params)
73 73
74 74 expected = 'repository `666` does not exist'
75 75 assert_error(id_, expected, given=response.body)
76 76
77 77 def test_api_get_pull_request_comments_pull_request_error(self):
78 78 id_, params = build_data(
79 79 self.apikey, 'get_pull_request_comments', pullrequestid=666)
80 80 response = api_call(self.app, params)
81 81
82 82 expected = 'pull request `666` does not exist'
83 83 assert_error(id_, expected, given=response.body)
@@ -1,81 +1,81 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.model.pull_request import PullRequestModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGetPullRequest(object):
31 31
32 32 @pytest.mark.backends("git", "hg")
33 33 def test_api_get_pull_requests(self, pr_util):
34 34 pull_request = pr_util.create_pull_request()
35 35 pull_request_2 = PullRequestModel().create(
36 36 created_by=pull_request.author,
37 37 source_repo=pull_request.source_repo,
38 38 source_ref=pull_request.source_ref,
39 39 target_repo=pull_request.target_repo,
40 40 target_ref=pull_request.target_ref,
41 41 revisions=pull_request.revisions,
42 42 reviewers=(),
43 43 observers=(),
44 44 title=pull_request.title,
45 45 description=pull_request.description,
46 46 )
47 47 Session().commit()
48 48 id_, params = build_data(
49 49 self.apikey, 'get_pull_requests',
50 50 repoid=pull_request.target_repo.repo_name)
51 51 response = api_call(self.app, params)
52 52 assert response.status == '200 OK'
53 53 assert len(response.json['result']) == 2
54 54
55 55 PullRequestModel().close_pull_request(
56 56 pull_request_2, pull_request_2.author)
57 57 Session().commit()
58 58
59 59 id_, params = build_data(
60 60 self.apikey, 'get_pull_requests',
61 61 repoid=pull_request.target_repo.repo_name,
62 62 status='new')
63 63 response = api_call(self.app, params)
64 64 assert response.status == '200 OK'
65 65 assert len(response.json['result']) == 1
66 66
67 67 id_, params = build_data(
68 68 self.apikey, 'get_pull_requests',
69 69 repoid=pull_request.target_repo.repo_name,
70 70 status='closed')
71 71 response = api_call(self.app, params)
72 72 assert response.status == '200 OK'
73 73 assert len(response.json['result']) == 1
74 74
75 75 @pytest.mark.backends("git", "hg")
76 76 def test_api_get_pull_requests_repo_error(self):
77 77 id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666)
78 78 response = api_call(self.app, params)
79 79
80 80 expected = 'repository `666` does not exist'
81 81 assert_error(id_, expected, given=response.body)
@@ -1,142 +1,142 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_ok, assert_error, expected_permissions)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestGetRepo(object):
33 33 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
34 34 ('apikey', True),
35 35 ('apikey_regular', False),
36 36 ])
37 37 @pytest.mark.parametrize("cache_param", [
38 38 True,
39 39 False,
40 40 None,
41 41 ])
42 42 def test_api_get_repo(
43 43 self, apikey_attr, expect_secrets, cache_param, backend,
44 44 user_util):
45 45 repo = backend.create_repo()
46 46 repo_id = repo.repo_id
47 47 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
48 48 group = user_util.create_user_group(members=[usr])
49 49 user_util.grant_user_group_permission_to_repo(
50 50 repo=repo, user_group=group, permission_name='repository.read')
51 51 Session().commit()
52 52 kwargs = {
53 53 'repoid': repo.repo_name,
54 54 }
55 55 if cache_param is not None:
56 56 kwargs['cache'] = cache_param
57 57
58 58 apikey = getattr(self, apikey_attr)
59 59 id_, params = build_data(apikey, 'get_repo', **kwargs)
60 60 response = api_call(self.app, params)
61 61
62 62 ret = repo.get_api_data()
63 63
64 64 permissions = expected_permissions(repo)
65 65
66 66 followers = []
67 67
68 68 repo = RepoModel().get(repo_id)
69 69 for user in repo.followers:
70 70 followers.append(user.user.get_api_data(
71 71 include_secrets=expect_secrets))
72 72
73 73 ret['permissions'] = permissions
74 74 ret['followers'] = followers
75 75
76 76 expected = ret
77 77
78 78 assert_ok(id_, expected, given=response.body)
79 79
80 80 @pytest.mark.parametrize("grant_perm", [
81 81 'repository.admin',
82 82 'repository.write',
83 83 'repository.read',
84 84 ])
85 85 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
86 86 # TODO: Depending on which tests are running before this one, we
87 87 # start with a different number of permissions in the database.
88 88 repo = RepoModel().get_by_repo_name(backend.repo_name)
89 89 repo_id = repo.repo_id
90 90 permission_count = len(repo.repo_to_perm)
91 91
92 92 RepoModel().grant_user_permission(repo=backend.repo_name,
93 93 user=self.TEST_USER_LOGIN,
94 94 perm=grant_perm)
95 95 Session().commit()
96 96 id_, params = build_data(
97 97 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
98 98 response = api_call(self.app, params)
99 99
100 100 repo = RepoModel().get_by_repo_name(backend.repo_name)
101 101 ret = repo.get_api_data()
102 102
103 103 assert permission_count + 1, len(repo.repo_to_perm)
104 104
105 105 permissions = expected_permissions(repo)
106 106
107 107 followers = []
108 108
109 109 repo = RepoModel().get(repo_id)
110 110 for user in repo.followers:
111 111 followers.append(user.user.get_api_data())
112 112
113 113 ret['permissions'] = permissions
114 114 ret['followers'] = followers
115 115
116 116 expected = ret
117 117 try:
118 118 assert_ok(id_, expected, given=response.body)
119 119 finally:
120 120 RepoModel().revoke_user_permission(
121 121 backend.repo_name, self.TEST_USER_LOGIN)
122 122
123 123 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
124 124 RepoModel().grant_user_permission(repo=backend.repo_name,
125 125 user=self.TEST_USER_LOGIN,
126 126 perm='repository.none')
127 127
128 128 id_, params = build_data(
129 129 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
130 130 response = api_call(self.app, params)
131 131
132 132 expected = 'repository `%s` does not exist' % (backend.repo_name)
133 133 assert_error(id_, expected, given=response.body)
134 134
135 135 def test_api_get_repo_not_existing(self):
136 136 id_, params = build_data(
137 137 self.apikey, 'get_repo', repoid='no-such-repo')
138 138 response = api_call(self.app, params)
139 139
140 140 ret = 'repository `%s` does not exist' % 'no-such-repo'
141 141 expected = ret
142 142 assert_error(id_, expected, given=response.body)
@@ -1,140 +1,140 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.api.tests.utils import build_data, api_call, assert_error
24 24
25 25
26 26 @pytest.mark.usefixtures("testuser_api", "app")
27 27 class TestGetRepoChangeset(object):
28 28 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
29 29 def test_get_repo_changeset(self, details, backend):
30 30 commit = backend.repo.get_commit(commit_idx=0)
31 31 __, params = build_data(
32 32 self.apikey, 'get_repo_changeset',
33 33 repoid=backend.repo_name, revision=commit.raw_id,
34 34 details=details,
35 35 )
36 36 response = api_call(self.app, params)
37 37 result = response.json['result']
38 38 assert result['revision'] == 0
39 39 assert result['raw_id'] == commit.raw_id
40 40
41 41 if details == 'full':
42 42 assert result['refs']['bookmarks'] == getattr(
43 43 commit, 'bookmarks', [])
44 44 branches = [commit.branch] if commit.branch else []
45 45 assert result['refs']['branches'] == branches
46 46 assert result['refs']['tags'] == commit.tags
47 47
48 48 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
49 49 def test_get_repo_changeset_bad_type(self, details, backend):
50 50 id_, params = build_data(
51 51 self.apikey, 'get_repo_changeset',
52 52 repoid=backend.repo_name, revision=0,
53 53 details=details,
54 54 )
55 55 response = api_call(self.app, params)
56 56 expected = "commit_id must be a string value got <class 'int'> instead"
57 57 assert_error(id_, expected, given=response.body)
58 58
59 59 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
60 60 def test_get_repo_changesets(self, details, backend):
61 61 limit = 2
62 62 commit = backend.repo.get_commit(commit_idx=0)
63 63 __, params = build_data(
64 64 self.apikey, 'get_repo_changesets',
65 65 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
66 66 details=details,
67 67 )
68 68 response = api_call(self.app, params)
69 69 result = response.json['result']
70 70 assert result
71 71 assert len(result) == limit
72 72 for x in range(limit):
73 73 assert result[x]['revision'] == x
74 74
75 75 if details == 'full':
76 76 for x in range(limit):
77 77 assert 'bookmarks' in result[x]['refs']
78 78 assert 'branches' in result[x]['refs']
79 79 assert 'tags' in result[x]['refs']
80 80
81 81 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
82 82 @pytest.mark.parametrize("start_rev, expected_revision", [
83 83 ("0", 0),
84 84 ("10", 10),
85 85 ("20", 20),
86 86 ])
87 87 @pytest.mark.backends("hg", "git")
88 88 def test_get_repo_changesets_commit_range(
89 89 self, details, backend, start_rev, expected_revision):
90 90 limit = 10
91 91 __, params = build_data(
92 92 self.apikey, 'get_repo_changesets',
93 93 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
94 94 details=details,
95 95 )
96 96 response = api_call(self.app, params)
97 97 result = response.json['result']
98 98 assert result
99 99 assert len(result) == limit
100 100 for i in range(limit):
101 101 assert result[i]['revision'] == int(expected_revision) + i
102 102
103 103 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
104 104 @pytest.mark.parametrize("start_rev, expected_revision", [
105 105 ("0", 0),
106 106 ("10", 9),
107 107 ("20", 19),
108 108 ])
109 109 def test_get_repo_changesets_commit_range_svn(
110 110 self, details, backend_svn, start_rev, expected_revision):
111 111
112 112 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
113 113 # in our API allows to pass in a "Commit ID" as well as a
114 114 # "Commit Index". In the case of Subversion it is not possible to
115 115 # distinguish these cases. As a workaround we implemented this
116 116 # behavior which gives a preference to see it as a "Commit ID".
117 117
118 118 limit = 10
119 119 __, params = build_data(
120 120 self.apikey, 'get_repo_changesets',
121 121 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
122 122 details=details,
123 123 )
124 124 response = api_call(self.app, params)
125 125 result = response.json['result']
126 126 assert result
127 127 assert len(result) == limit
128 128 for i in range(limit):
129 129 assert result[i]['revision'] == int(expected_revision) + i
130 130
131 131 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
132 132 def test_get_repo_changesets_bad_type(self, details, backend):
133 133 id_, params = build_data(
134 134 self.apikey, 'get_repo_changesets',
135 135 repoid=backend.repo_name, start_rev=0, limit=2,
136 136 details=details,
137 137 )
138 138 response = api_call(self.app, params)
139 139 expected = "commit_id must be a string value got <class 'int'> instead"
140 140 assert_error(id_, expected, given=response.body)
@@ -1,141 +1,141 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import User, ChangesetComment
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.comment import CommentsModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_call_ok)
28 28
29 29
30 30 @pytest.fixture()
31 31 def make_repo_comments_factory(request):
32 32
33 33 class Make(object):
34 34
35 35 def make_comments(self, repo):
36 36 user = User.get_first_super_admin()
37 37 commit = repo.scm_instance()[0]
38 38
39 39 commit_id = commit.raw_id
40 40 file_0 = commit.affected_files[0]
41 41 comments = []
42 42
43 43 # general
44 44 comment = CommentsModel().create(
45 45 text='General Comment', repo=repo, user=user, commit_id=commit_id,
46 46 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
47 47 comments.append(comment)
48 48
49 49 # inline
50 50 comment = CommentsModel().create(
51 51 text='Inline Comment', repo=repo, user=user, commit_id=commit_id,
52 52 f_path=file_0, line_no='n1',
53 53 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
54 54 comments.append(comment)
55 55
56 56 # todo
57 57 comment = CommentsModel().create(
58 58 text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id,
59 59 f_path=file_0, line_no='n1',
60 60 comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False)
61 61 comments.append(comment)
62 62
63 63 return comments
64 64
65 65 return Make()
66 66
67 67
68 68 @pytest.mark.usefixtures("testuser_api", "app")
69 69 class TestGetRepo(object):
70 70
71 71 @pytest.mark.parametrize('filters, expected_count', [
72 72 ({}, 3),
73 73 ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2),
74 74 ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1),
75 75 ({'commit_id': 'FILLED DYNAMIC'}, 3),
76 76 ])
77 77 def test_api_get_repo_comments(self, backend, user_util,
78 78 make_repo_comments_factory, filters, expected_count):
79 79 commits = [{'message': 'A'}, {'message': 'B'}]
80 80 repo = backend.create_repo(commits=commits)
81 81 make_repo_comments_factory.make_comments(repo)
82 82
83 83 api_call_params = {'repoid': repo.repo_name,}
84 84 api_call_params.update(filters)
85 85
86 86 if 'commit_id' in api_call_params:
87 87 commit = repo.scm_instance()[0]
88 88 commit_id = commit.raw_id
89 89 api_call_params['commit_id'] = commit_id
90 90
91 91 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
92 92 response = api_call(self.app, params)
93 93 result = assert_call_ok(id_, given=response.body)
94 94
95 95 assert len(result) == expected_count
96 96
97 97 def test_api_get_repo_comments_wrong_comment_type(
98 98 self, make_repo_comments_factory, backend_hg):
99 99 commits = [{'message': 'A'}, {'message': 'B'}]
100 100 repo = backend_hg.create_repo(commits=commits)
101 101 make_repo_comments_factory.make_comments(repo)
102 102
103 103 api_call_params = {'repoid': repo.repo_name}
104 104 api_call_params.update({'comment_type': 'bogus'})
105 105
106 106 expected = 'comment_type must be one of `{}` got {}'.format(
107 107 ChangesetComment.COMMENT_TYPES, 'bogus')
108 108 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
109 109 response = api_call(self.app, params)
110 110 assert_error(id_, expected, given=response.body)
111 111
112 112 def test_api_get_comment(self, make_repo_comments_factory, backend_hg):
113 113 commits = [{'message': 'A'}, {'message': 'B'}]
114 114 repo = backend_hg.create_repo(commits=commits)
115 115
116 116 comments = make_repo_comments_factory.make_comments(repo)
117 117 comment_ids = [x.comment_id for x in comments]
118 118 Session().commit()
119 119
120 120 for comment_id in comment_ids:
121 121 id_, params = build_data(self.apikey, 'get_comment',
122 122 **{'comment_id': comment_id})
123 123 response = api_call(self.app, params)
124 124 result = assert_call_ok(id_, given=response.body)
125 125 assert result['comment_id'] == comment_id
126 126
127 127 def test_api_get_comment_no_access(self, make_repo_comments_factory, backend_hg, user_util):
128 128 commits = [{'message': 'A'}, {'message': 'B'}]
129 129 repo = backend_hg.create_repo(commits=commits)
130 130 comments = make_repo_comments_factory.make_comments(repo)
131 131 comment_id = comments[0].comment_id
132 132
133 133 test_user = user_util.create_user()
134 134 user_util.grant_user_permission_to_repo(repo, test_user, 'repository.none')
135 135
136 136 id_, params = build_data(test_user.api_key, 'get_comment',
137 137 **{'comment_id': comment_id})
138 138 response = api_call(self.app, params)
139 139 assert_error(id_,
140 140 expected='comment `{}` does not exist'.format(comment_id),
141 141 given=response.body)
@@ -1,54 +1,54 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.repo_group import RepoGroupModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, assert_error, expected_permissions)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApiGetRepoGroup(object):
30 30 def test_api_get_repo_group(self, user_util):
31 31 repo_group = user_util.create_repo_group()
32 32 repo_group_name = repo_group.group_name
33 33
34 34 id_, params = build_data(
35 35 self.apikey, 'get_repo_group', repogroupid=repo_group_name)
36 36 response = api_call(self.app, params)
37 37
38 38 repo_group = RepoGroupModel()._get_repo_group(repo_group_name)
39 39 ret = repo_group.get_api_data()
40 40
41 41 permissions = expected_permissions(repo_group)
42 42
43 43 ret['permissions'] = permissions
44 44 expected = ret
45 45 assert_ok(id_, expected, given=response.body)
46 46
47 47 def test_api_get_repo_group_not_existing(self):
48 48 id_, params = build_data(
49 49 self.apikey, 'get_repo_group', repogroupid='no-such-repo-group')
50 50 response = api_call(self.app, params)
51 51
52 52 ret = 'repository group `%s` does not exist' % 'no-such-repo-group'
53 53 expected = ret
54 54 assert_error(id_, expected, given=response.body)
@@ -1,39 +1,39 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.repo_group import RepoGroupModel
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestApiGetRepoGroups(object):
29 29 def test_api_get_repo_groups(self):
30 30 id_, params = build_data(self.apikey, 'get_repo_groups')
31 31 response = api_call(self.app, params)
32 32
33 33 result = []
34 34 for repo in RepoGroupModel().get_all():
35 35 result.append(repo.get_api_data())
36 36 ret = jsonify(result)
37 37
38 38 expected = ret
39 39 assert_ok(id_, expected, given=response.body)
@@ -1,141 +1,141 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGetRepoNodes(object):
31 31 @pytest.mark.parametrize("name, ret_type", [
32 32 ('all', 'all'),
33 33 ('dirs', 'dirs'),
34 34 ('files', 'files'),
35 35 ])
36 36 def test_api_get_repo_nodes(self, name, ret_type, backend):
37 37 commit_id = 'tip'
38 38 path = '/'
39 39 id_, params = build_data(
40 40 self.apikey, 'get_repo_nodes',
41 41 repoid=backend.repo_name, revision=commit_id,
42 42 root_path=path,
43 43 ret_type=ret_type)
44 44 response = api_call(self.app, params)
45 45
46 46 # we don't the actual return types here since it's tested somewhere
47 47 # else
48 48 expected = response.json['result']
49 49 assert_ok(id_, expected, given=response.body)
50 50
51 51 def test_api_get_repo_nodes_bad_commits(self, backend):
52 52 commit_id = 'i-dont-exist'
53 53 path = '/'
54 54 id_, params = build_data(
55 55 self.apikey, 'get_repo_nodes',
56 56 repoid=backend.repo_name, revision=commit_id,
57 57 root_path=path, )
58 58 response = api_call(self.app, params)
59 59
60 60 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
61 61 assert_error(id_, expected, given=response.body)
62 62
63 63 def test_api_get_repo_nodes_bad_path(self, backend):
64 64 commit_id = 'tip'
65 65 path = '/idontexits'
66 66 id_, params = build_data(
67 67 self.apikey, 'get_repo_nodes',
68 68 repoid=backend.repo_name, revision=commit_id,
69 69 root_path=path, )
70 70 response = api_call(self.app, params)
71 71
72 72 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
73 73 assert_error(id_, expected, given=response.body)
74 74
75 75 def test_api_get_repo_nodes_max_file_bytes(self, backend):
76 76 commit_id = 'tip'
77 77 path = '/'
78 78 max_file_bytes = 500
79 79
80 80 id_, params = build_data(
81 81 self.apikey, 'get_repo_nodes',
82 82 repoid=backend.repo_name, revision=commit_id, details='full',
83 83 root_path=path)
84 84 response = api_call(self.app, params)
85 85 assert any(file['content'] and len(file['content']) > max_file_bytes
86 86 for file in response.json['result'])
87 87
88 88 id_, params = build_data(
89 89 self.apikey, 'get_repo_nodes',
90 90 repoid=backend.repo_name, revision=commit_id,
91 91 root_path=path, details='full',
92 92 max_file_bytes=max_file_bytes)
93 93 response = api_call(self.app, params)
94 94 assert all(
95 95 file['content'] is None if file['size'] > max_file_bytes else True
96 96 for file in response.json['result'])
97 97
98 98 def test_api_get_repo_nodes_bad_ret_type(self, backend):
99 99 commit_id = 'tip'
100 100 path = '/'
101 101 ret_type = 'error'
102 102 id_, params = build_data(
103 103 self.apikey, 'get_repo_nodes',
104 104 repoid=backend.repo_name, revision=commit_id,
105 105 root_path=path,
106 106 ret_type=ret_type)
107 107 response = api_call(self.app, params)
108 108
109 109 expected = ('ret_type must be one of %s'
110 110 % (','.join(['all', 'dirs', 'files'])))
111 111 assert_error(id_, expected, given=response.body)
112 112
113 113 @pytest.mark.parametrize("name, ret_type, grant_perm", [
114 114 ('all', 'all', 'repository.write'),
115 115 ('dirs', 'dirs', 'repository.admin'),
116 116 ('files', 'files', 'repository.read'),
117 117 ])
118 118 def test_api_get_repo_nodes_by_regular_user(
119 119 self, name, ret_type, grant_perm, backend):
120 120 RepoModel().grant_user_permission(repo=backend.repo_name,
121 121 user=self.TEST_USER_LOGIN,
122 122 perm=grant_perm)
123 123 Session().commit()
124 124
125 125 commit_id = 'tip'
126 126 path = '/'
127 127 id_, params = build_data(
128 128 self.apikey_regular, 'get_repo_nodes',
129 129 repoid=backend.repo_name, revision=commit_id,
130 130 root_path=path,
131 131 ret_type=ret_type)
132 132 response = api_call(self.app, params)
133 133
134 134 # we don't the actual return types here since it's tested somewhere
135 135 # else
136 136 expected = response.json['result']
137 137 try:
138 138 assert_ok(id_, expected, given=response.body)
139 139 finally:
140 140 RepoModel().revoke_user_permission(
141 141 backend.repo_name, self.TEST_USER_LOGIN)
@@ -1,39 +1,39 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_ok, assert_error, expected_permissions)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestGetRepo(object):
33 33 def test_api_get_repo_refs(self, backend, user_util):
34 34 repo = backend.create_repo()
35 35 id_, params = build_data(self.apikey, 'get_repo_refs',
36 36 **{'repoid': repo.repo_name,})
37 37 response = api_call(self.app, params)
38 38 expected = repo.scm_instance().refs()
39 39 assert_ok(id_, expected, given=response.body)
@@ -1,128 +1,128 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.repo import RepoModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, assert_error, jsonify)
26 26 from rhodecode.model.db import User
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGetRepos(object):
31 31 def test_api_get_repos(self):
32 32 id_, params = build_data(self.apikey, 'get_repos')
33 33 response = api_call(self.app, params)
34 34
35 35 result = []
36 36 for repo in RepoModel().get_all():
37 37 result.append(repo.get_api_data(include_secrets=True))
38 38 ret = jsonify(result)
39 39
40 40 expected = ret
41 41 assert_ok(id_, expected, given=response.body)
42 42
43 43 def test_api_get_repos_only_toplevel(self, user_util):
44 44 repo_group = user_util.create_repo_group(auto_cleanup=True)
45 45 user_util.create_repo(parent=repo_group)
46 46
47 47 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
48 48 response = api_call(self.app, params)
49 49
50 50 result = []
51 51 for repo in RepoModel().get_repos_for_root(root=None):
52 52 result.append(repo.get_api_data(include_secrets=True))
53 53 expected = jsonify(result)
54 54
55 55 assert_ok(id_, expected, given=response.body)
56 56
57 57 def test_api_get_repos_with_wrong_root(self):
58 58 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
59 59 response = api_call(self.app, params)
60 60
61 61 expected = 'Root repository group `abracadabra` does not exist'
62 62 assert_error(id_, expected, given=response.body)
63 63
64 64 def test_api_get_repos_with_root(self, user_util):
65 65 repo_group = user_util.create_repo_group(auto_cleanup=True)
66 66 repo_group_name = repo_group.group_name
67 67
68 68 user_util.create_repo(parent=repo_group)
69 69 user_util.create_repo(parent=repo_group)
70 70
71 71 # nested, should not show up
72 72 user_util._test_name = '{}/'.format(repo_group_name)
73 73 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
74 74 user_util.create_repo(parent=sub_repo_group)
75 75
76 76 id_, params = build_data(self.apikey, 'get_repos',
77 77 root=repo_group_name, traverse=0)
78 78 response = api_call(self.app, params)
79 79
80 80 result = []
81 81 for repo in RepoModel().get_repos_for_root(repo_group):
82 82 result.append(repo.get_api_data(include_secrets=True))
83 83
84 84 assert len(result) == 2
85 85 expected = jsonify(result)
86 86 assert_ok(id_, expected, given=response.body)
87 87
88 88 def test_api_get_repos_with_root_and_traverse(self, user_util):
89 89 repo_group = user_util.create_repo_group(auto_cleanup=True)
90 90 repo_group_name = repo_group.group_name
91 91
92 92 user_util.create_repo(parent=repo_group)
93 93 user_util.create_repo(parent=repo_group)
94 94
95 95 # nested, should not show up
96 96 user_util._test_name = '{}/'.format(repo_group_name)
97 97 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
98 98 user_util.create_repo(parent=sub_repo_group)
99 99
100 100 id_, params = build_data(self.apikey, 'get_repos',
101 101 root=repo_group_name, traverse=1)
102 102 response = api_call(self.app, params)
103 103
104 104 result = []
105 105 for repo in RepoModel().get_repos_for_root(
106 106 repo_group_name, traverse=True):
107 107 result.append(repo.get_api_data(include_secrets=True))
108 108
109 109 assert len(result) == 3
110 110 expected = jsonify(result)
111 111 assert_ok(id_, expected, given=response.body)
112 112
113 113 def test_api_get_repos_non_admin(self):
114 114 id_, params = build_data(self.apikey_regular, 'get_repos')
115 115 response = api_call(self.app, params)
116 116
117 117 user = User.get_by_username(self.TEST_USER_LOGIN)
118 118 allowed_repos = user.AuthUser().permissions['repositories']
119 119
120 120 result = []
121 121 for repo in RepoModel().get_all():
122 122 perm = allowed_repos[repo.repo_name]
123 123 if perm in ['repository.read', 'repository.write', 'repository.admin']:
124 124 result.append(repo.get_api_data())
125 125 ret = jsonify(result)
126 126
127 127 expected = ret
128 128 assert_ok(id_, expected, given=response.body)
@@ -1,83 +1,83 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.scm import ScmModel
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.fixture()
28 28 def http_host_stub():
29 29 """
30 30 To ensure that we can get an IP address, this test shall run with a
31 31 hostname set to "localhost".
32 32 """
33 33 return 'localhost:80'
34 34
35 35
36 36 @pytest.mark.usefixtures("testuser_api", "app")
37 37 class TestGetServerInfo(object):
38 38 def test_api_get_server_info(self):
39 39 id_, params = build_data(self.apikey, 'get_server_info')
40 40 response = api_call(self.app, params)
41 41 resp = response.json
42 42 expected = ScmModel().get_server_info()
43 43 expected['memory'] = resp['result']['memory']
44 44 expected['uptime'] = resp['result']['uptime']
45 45 expected['load'] = resp['result']['load']
46 46 expected['cpu'] = resp['result']['cpu']
47 47 expected['storage'] = resp['result']['storage']
48 48 expected['storage_temp'] = resp['result']['storage_temp']
49 49 expected['storage_inodes'] = resp['result']['storage_inodes']
50 50 expected['server'] = resp['result']['server']
51 51
52 52 expected['index_storage'] = resp['result']['index_storage']
53 53 expected['storage'] = resp['result']['storage']
54 54
55 55 assert_ok(id_, expected, given=response.body)
56 56
57 57 def test_api_get_server_info_ip(self):
58 58 id_, params = build_data(self.apikey, 'get_server_info')
59 59 response = api_call(self.app, params)
60 60 resp = response.json
61 61 expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'})
62 62 expected['memory'] = resp['result']['memory']
63 63 expected['uptime'] = resp['result']['uptime']
64 64 expected['load'] = resp['result']['load']
65 65 expected['cpu'] = resp['result']['cpu']
66 66 expected['storage'] = resp['result']['storage']
67 67 expected['storage_temp'] = resp['result']['storage_temp']
68 68 expected['storage_inodes'] = resp['result']['storage_inodes']
69 69 expected['server'] = resp['result']['server']
70 70
71 71 expected['index_storage'] = resp['result']['index_storage']
72 72 expected['storage'] = resp['result']['storage']
73 73
74 74 assert_ok(id_, expected, given=response.body)
75 75
76 76 def test_api_get_server_info_data_for_search_index_build(self):
77 77 id_, params = build_data(self.apikey, 'get_server_info')
78 78 response = api_call(self.app, params)
79 79 resp = response.json
80 80
81 81 # required by indexer
82 82 assert resp['result']['index_storage']
83 83 assert resp['result']['storage']
@@ -1,85 +1,85 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.lib.auth import AuthUser
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGetUser(object):
31 31 def test_api_get_user(self):
32 32 id_, params = build_data(
33 33 self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN)
34 34 response = api_call(self.app, params)
35 35
36 36 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
37 37 ret = usr.get_api_data(include_secrets=True)
38 38 permissions = AuthUser(usr.user_id).permissions
39 39 ret['permissions'] = permissions
40 40 ret['permissions_summary'] = permissions
41 41
42 42 expected = ret
43 43 assert_ok(id_, expected, given=response.body)
44 44
45 45 def test_api_get_user_not_existing(self):
46 46 id_, params = build_data(self.apikey, 'get_user', userid='trololo')
47 47 response = api_call(self.app, params)
48 48
49 49 expected = "user `%s` does not exist" % 'trololo'
50 50 assert_error(id_, expected, given=response.body)
51 51
52 52 def test_api_get_user_without_giving_userid(self):
53 53 id_, params = build_data(self.apikey, 'get_user')
54 54 response = api_call(self.app, params)
55 55
56 56 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
57 57 ret = usr.get_api_data(include_secrets=True)
58 58 permissions = AuthUser(usr.user_id).permissions
59 59 ret['permissions'] = permissions
60 60 ret['permissions_summary'] = permissions
61 61
62 62 expected = ret
63 63 assert_ok(id_, expected, given=response.body)
64 64
65 65 def test_api_get_user_without_giving_userid_non_admin(self):
66 66 id_, params = build_data(self.apikey_regular, 'get_user')
67 67 response = api_call(self.app, params)
68 68
69 69 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
70 70 ret = usr.get_api_data(include_secrets=True)
71 71 permissions = AuthUser(usr.user_id).permissions
72 72 ret['permissions'] = permissions
73 73 ret['permissions_summary'] = permissions
74 74
75 75 expected = ret
76 76 assert_ok(id_, expected, given=response.body)
77 77
78 78 def test_api_get_user_with_giving_userid_non_admin(self):
79 79 id_, params = build_data(
80 80 self.apikey_regular, 'get_user',
81 81 userid=self.TEST_USER_LOGIN)
82 82 response = api_call(self.app, params)
83 83
84 84 expected = 'userid is not the same as your user'
85 85 assert_error(id_, expected, given=response.body)
@@ -1,75 +1,75 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.user import UserModel
23 23 from rhodecode.api.tests.utils import (
24 24 build_data, api_call, assert_ok, assert_error, expected_permissions)
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetUserGroups(object):
29 29 def test_api_get_user_group(self, user_util):
30 30 user, group = user_util.create_user_with_group()
31 31 id_, params = build_data(
32 32 self.apikey, 'get_user_group', usergroupid=group.users_group_name)
33 33 response = api_call(self.app, params)
34 34
35 35 ret = group.get_api_data()
36 36 ret['users'] = [user.get_api_data()]
37 37
38 38 permissions = expected_permissions(group)
39 39
40 40 ret['permissions'] = permissions
41 41 ret['permissions_summary'] = response.json['result']['permissions_summary']
42 42 expected = ret
43 43 assert_ok(id_, expected, given=response.body)
44 44
45 45 def test_api_get_user_group_regular_user(self, user_util):
46 46 user, group = user_util.create_user_with_group()
47 47 id_, params = build_data(
48 48 self.apikey_regular, 'get_user_group',
49 49 usergroupid=group.users_group_name)
50 50 response = api_call(self.app, params)
51 51
52 52 ret = group.get_api_data()
53 53 ret['users'] = [user.get_api_data()]
54 54
55 55 permissions = expected_permissions(group)
56 56
57 57 ret['permissions'] = permissions
58 58 ret['permissions_summary'] = response.json['result']['permissions_summary']
59 59 expected = ret
60 60 assert_ok(id_, expected, given=response.body)
61 61
62 62 def test_api_get_user_group_regular_user_permission_denied(
63 63 self, user_util):
64 64 group = user_util.create_user_group()
65 65 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
66 66 group_name = group.users_group_name
67 67 user_util.grant_user_permission_to_user_group(
68 68 group, user, 'usergroup.none')
69 69
70 70 id_, params = build_data(
71 71 self.apikey_regular, 'get_user_group', usergroupid=group_name)
72 72 response = api_call(self.app, params)
73 73
74 74 expected = 'user group `%s` does not exist' % (group_name,)
75 75 assert_error(id_, expected, given=response.body)
@@ -1,69 +1,69 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.api.tests.utils import build_data, api_call
25 25 from rhodecode.lib.ext_json import json
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestGetUserGroups(object):
30 30 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
31 31 ('apikey', True),
32 32 ('apikey_regular', False),
33 33 ])
34 34 def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util):
35 35 first_group = user_util.create_user_group()
36 36 second_group = user_util.create_user_group()
37 37 expected = [
38 38 g.get_api_data(include_secrets=expect_secrets)
39 39 for g in (first_group, second_group)]
40 40
41 41 apikey = getattr(self, apikey_attr)
42 42 id_, params = build_data(apikey, 'get_user_groups', )
43 43 response = api_call(self.app, params)
44 44 self._assert_ok(id_, expected, response)
45 45
46 46 def test_api_get_user_groups_regular_user(self, user_util):
47 47 first_group = user_util.create_user_group()
48 48 second_group = user_util.create_user_group()
49 49 expected = [g.get_api_data() for g in (first_group, second_group)]
50 50
51 51 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
52 52 response = api_call(self.app, params)
53 53 self._assert_ok(id_, expected, response)
54 54
55 55 def test_api_get_user_groups_regular_user_no_permission(self, user_util):
56 56 group = user_util.create_user_group()
57 57 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
58 58 user_util.grant_user_permission_to_user_group(
59 59 group, user, 'usergroup.none')
60 60 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
61 61 response = api_call(self.app, params)
62 62 expected = []
63 63 self._assert_ok(id_, expected, response)
64 64
65 65 def _assert_ok(self, id_, expected_list, response):
66 66 result = json.loads(response.body)
67 67 assert result['id'] == id_
68 68 assert result['error'] is None
69 69 assert result['result'] == expected_list
@@ -1,39 +1,39 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.db import User
23 23 from rhodecode.api.tests.utils import (
24 24 build_data, api_call, assert_ok, jsonify)
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetUsers(object):
29 29 def test_api_get_users(self):
30 30 id_, params = build_data(self.apikey, 'get_users', )
31 31 response = api_call(self.app, params)
32 32 ret_all = []
33 33 _users = User.query().filter(User.username != User.DEFAULT_USER) \
34 34 .order_by(User.username).all()
35 35 for usr in _users:
36 36 ret = usr.get_api_data(include_secrets=True)
37 37 ret_all.append(jsonify(ret))
38 38 expected = ret_all
39 39 assert_ok(id_, expected, given=response.body)
@@ -1,89 +1,89 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.repo import RepoModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestGrantUserGroupPermission(object):
30 30 @pytest.mark.parametrize("name, perm", [
31 31 ('none', 'repository.none'),
32 32 ('read', 'repository.read'),
33 33 ('write', 'repository.write'),
34 34 ('admin', 'repository.admin')
35 35 ])
36 36 def test_api_grant_user_group_permission(
37 37 self, name, perm, backend, user_util):
38 38 user_group = user_util.create_user_group()
39 39 id_, params = build_data(
40 40 self.apikey,
41 41 'grant_user_group_permission',
42 42 repoid=backend.repo_name,
43 43 usergroupid=user_group.users_group_name,
44 44 perm=perm)
45 45 response = api_call(self.app, params)
46 46
47 47 ret = {
48 48 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
49 49 perm, user_group.users_group_name, backend.repo_name
50 50 ),
51 51 'success': True
52 52 }
53 53 expected = ret
54 54 assert_ok(id_, expected, given=response.body)
55 55
56 56 def test_api_grant_user_group_permission_wrong_permission(
57 57 self, backend, user_util):
58 58 perm = 'haha.no.permission'
59 59 user_group = user_util.create_user_group()
60 60 id_, params = build_data(
61 61 self.apikey,
62 62 'grant_user_group_permission',
63 63 repoid=backend.repo_name,
64 64 usergroupid=user_group.users_group_name,
65 65 perm=perm)
66 66 response = api_call(self.app, params)
67 67
68 68 expected = 'permission `%s` does not exist.' % (perm,)
69 69 assert_error(id_, expected, given=response.body)
70 70
71 71 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
72 72 def test_api_grant_user_group_permission_exception_when_adding(
73 73 self, backend, user_util):
74 74 perm = 'repository.read'
75 75 user_group = user_util.create_user_group()
76 76 id_, params = build_data(
77 77 self.apikey,
78 78 'grant_user_group_permission',
79 79 repoid=backend.repo_name,
80 80 usergroupid=user_group.users_group_name,
81 81 perm=perm)
82 82 response = api_call(self.app, params)
83 83
84 84 expected = (
85 85 'failed to edit permission for user group: `%s` in repo: `%s`' % (
86 86 user_group.users_group_name, backend.repo_name
87 87 )
88 88 )
89 89 assert_error(id_, expected, given=response.body)
@@ -1,172 +1,172 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGrantUserGroupPermissionFromRepoGroup(object):
31 31 @pytest.mark.parametrize("name, perm, apply_to_children", [
32 32 ('none', 'group.none', 'none'),
33 33 ('read', 'group.read', 'none'),
34 34 ('write', 'group.write', 'none'),
35 35 ('admin', 'group.admin', 'none'),
36 36
37 37 ('none', 'group.none', 'all'),
38 38 ('read', 'group.read', 'all'),
39 39 ('write', 'group.write', 'all'),
40 40 ('admin', 'group.admin', 'all'),
41 41
42 42 ('none', 'group.none', 'repos'),
43 43 ('read', 'group.read', 'repos'),
44 44 ('write', 'group.write', 'repos'),
45 45 ('admin', 'group.admin', 'repos'),
46 46
47 47 ('none', 'group.none', 'groups'),
48 48 ('read', 'group.read', 'groups'),
49 49 ('write', 'group.write', 'groups'),
50 50 ('admin', 'group.admin', 'groups'),
51 51 ])
52 52 def test_api_grant_user_group_permission_to_repo_group(
53 53 self, name, perm, apply_to_children, user_util):
54 54 user_group = user_util.create_user_group()
55 55 repo_group = user_util.create_repo_group()
56 56 user_util.create_repo(parent=repo_group)
57 57
58 58 id_, params = build_data(
59 59 self.apikey,
60 60 'grant_user_group_permission_to_repo_group',
61 61 repogroupid=repo_group.name,
62 62 usergroupid=user_group.users_group_name,
63 63 perm=perm,
64 64 apply_to_children=apply_to_children,)
65 65 response = api_call(self.app, params)
66 66
67 67 ret = {
68 68 'msg': (
69 69 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
70 70 ' in repo group: `%s`' % (
71 71 perm, apply_to_children, user_group.users_group_name,
72 72 repo_group.name
73 73 )
74 74 ),
75 75 'success': True
76 76 }
77 77 expected = ret
78 78 try:
79 79 assert_ok(id_, expected, given=response.body)
80 80 finally:
81 81 RepoGroupModel().revoke_user_group_permission(
82 82 repo_group.group_id, user_group.users_group_id)
83 83
84 84 @pytest.mark.parametrize(
85 85 "name, perm, apply_to_children, grant_admin, access_ok", [
86 86 ('none_fails', 'group.none', 'none', False, False),
87 87 ('read_fails', 'group.read', 'none', False, False),
88 88 ('write_fails', 'group.write', 'none', False, False),
89 89 ('admin_fails', 'group.admin', 'none', False, False),
90 90
91 91 # with granted perms
92 92 ('none_ok', 'group.none', 'none', True, True),
93 93 ('read_ok', 'group.read', 'none', True, True),
94 94 ('write_ok', 'group.write', 'none', True, True),
95 95 ('admin_ok', 'group.admin', 'none', True, True),
96 96 ]
97 97 )
98 98 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
99 99 self, name, perm, apply_to_children, grant_admin, access_ok,
100 100 user_util):
101 101 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
102 102 user_group = user_util.create_user_group()
103 103 repo_group = user_util.create_repo_group()
104 104 if grant_admin:
105 105 user_util.grant_user_permission_to_repo_group(
106 106 repo_group, user, 'group.admin')
107 107
108 108 id_, params = build_data(
109 109 self.apikey_regular,
110 110 'grant_user_group_permission_to_repo_group',
111 111 repogroupid=repo_group.name,
112 112 usergroupid=user_group.users_group_name,
113 113 perm=perm,
114 114 apply_to_children=apply_to_children,)
115 115 response = api_call(self.app, params)
116 116 if access_ok:
117 117 ret = {
118 118 'msg': (
119 119 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
120 120 ' in repo group: `%s`' % (
121 121 perm, apply_to_children, user_group.users_group_name,
122 122 repo_group.name
123 123 )
124 124 ),
125 125 'success': True
126 126 }
127 127 expected = ret
128 128 try:
129 129 assert_ok(id_, expected, given=response.body)
130 130 finally:
131 131 RepoGroupModel().revoke_user_group_permission(
132 132 repo_group.group_id, user_group.users_group_id)
133 133 else:
134 134 expected = 'repository group `%s` does not exist' % (repo_group.name,)
135 135 assert_error(id_, expected, given=response.body)
136 136
137 137 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
138 138 self, user_util):
139 139 user_group = user_util.create_user_group()
140 140 repo_group = user_util.create_repo_group()
141 141 perm = 'haha.no.permission'
142 142 id_, params = build_data(
143 143 self.apikey,
144 144 'grant_user_group_permission_to_repo_group',
145 145 repogroupid=repo_group.name,
146 146 usergroupid=user_group.users_group_name,
147 147 perm=perm)
148 148 response = api_call(self.app, params)
149 149
150 150 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,)
151 151 assert_error(id_, expected, given=response.body)
152 152
153 153 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
154 154 def test_api_grant_user_group_permission_exception_when_adding_2(
155 155 self, user_util):
156 156 user_group = user_util.create_user_group()
157 157 repo_group = user_util.create_repo_group()
158 158 perm = 'group.read'
159 159 id_, params = build_data(
160 160 self.apikey,
161 161 'grant_user_group_permission_to_repo_group',
162 162 repogroupid=repo_group.name,
163 163 usergroupid=user_group.users_group_name,
164 164 perm=perm)
165 165 response = api_call(self.app, params)
166 166
167 167 expected = (
168 168 'failed to edit permission for user group: `%s`'
169 169 ' in repo group: `%s`' % (
170 170 user_group.users_group_name, repo_group.name)
171 171 )
172 172 assert_error(id_, expected, given=response.body)
@@ -1,96 +1,96 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.user_group import UserGroupModel
23 23 from rhodecode.api.tests.utils import (
24 24 build_data, api_call, assert_ok, assert_error)
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGrantUserGroupPermissionFromUserGroup(object):
29 29 @pytest.mark.parametrize("name, perm", [
30 30 ('none', 'usergroup.none'),
31 31 ('read', 'usergroup.read'),
32 32 ('write', 'usergroup.write'),
33 33 ('admin', 'usergroup.admin'),
34 34
35 35 ('none', 'usergroup.none'),
36 36 ('read', 'usergroup.read'),
37 37 ('write', 'usergroup.write'),
38 38 ('admin', 'usergroup.admin'),
39 39
40 40 ('none', 'usergroup.none'),
41 41 ('read', 'usergroup.read'),
42 42 ('write', 'usergroup.write'),
43 43 ('admin', 'usergroup.admin'),
44 44
45 45 ('none', 'usergroup.none'),
46 46 ('read', 'usergroup.read'),
47 47 ('write', 'usergroup.write'),
48 48 ('admin', 'usergroup.admin'),
49 49 ])
50 50 def test_api_grant_user_group_permission_to_user_group(
51 51 self, name, perm, user_util):
52 52 group = user_util.create_user_group()
53 53 target_group = user_util.create_user_group()
54 54
55 55 id_, params = build_data(
56 56 self.apikey,
57 57 'grant_user_group_permission_to_user_group',
58 58 usergroupid=target_group.users_group_name,
59 59 sourceusergroupid=group.users_group_name,
60 60 perm=perm)
61 61 response = api_call(self.app, params)
62 62
63 63 expected = {
64 64 'msg': (
65 65 'Granted perm: `%s` for user group: `%s`'
66 66 ' in user group: `%s`' % (
67 67 perm, group.users_group_name,
68 68 target_group.users_group_name
69 69 )
70 70 ),
71 71 'success': True
72 72 }
73 73 try:
74 74 assert_ok(id_, expected, given=response.body)
75 75 finally:
76 76 UserGroupModel().revoke_user_group_permission(
77 77 target_group.users_group_id, group.users_group_id)
78 78
79 79 def test_api_grant_user_group_permission_to_user_group_same_failure(
80 80 self, user_util):
81 81 group = user_util.create_user_group()
82 82
83 83 id_, params = build_data(
84 84 self.apikey,
85 85 'grant_user_group_permission_to_user_group',
86 86 usergroupid=group.users_group_name,
87 87 sourceusergroupid=group.users_group_name,
88 88 perm='usergroup.none')
89 89 response = api_call(self.app, params)
90 90
91 91 expected = (
92 92 'failed to edit permission for user group: `%s`'
93 93 ' in user group: `%s`' % (
94 94 group.users_group_name, group.users_group_name)
95 95 )
96 96 assert_error(id_, expected, given=response.body)
@@ -1,86 +1,86 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.repo import RepoModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestGrantUserPermission(object):
30 30 @pytest.mark.parametrize("name, perm", [
31 31 ('none', 'repository.none'),
32 32 ('read', 'repository.read'),
33 33 ('write', 'repository.write'),
34 34 ('admin', 'repository.admin')
35 35 ])
36 36 def test_api_grant_user_permission(self, name, perm, backend, user_util):
37 37 user = user_util.create_user()
38 38 id_, params = build_data(
39 39 self.apikey,
40 40 'grant_user_permission',
41 41 repoid=backend.repo_name,
42 42 userid=user.username,
43 43 perm=perm)
44 44 response = api_call(self.app, params)
45 45
46 46 ret = {
47 47 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
48 48 perm, user.username, backend.repo_name
49 49 ),
50 50 'success': True
51 51 }
52 52 expected = ret
53 53 assert_ok(id_, expected, given=response.body)
54 54
55 55 def test_api_grant_user_permission_wrong_permission(
56 56 self, backend, user_util):
57 57 user = user_util.create_user()
58 58 perm = 'haha.no.permission'
59 59 id_, params = build_data(
60 60 self.apikey,
61 61 'grant_user_permission',
62 62 repoid=backend.repo_name,
63 63 userid=user.username,
64 64 perm=perm)
65 65 response = api_call(self.app, params)
66 66
67 67 expected = 'permission `%s` does not exist.' % (perm,)
68 68 assert_error(id_, expected, given=response.body)
69 69
70 70 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
71 71 def test_api_grant_user_permission_exception_when_adding(
72 72 self, backend, user_util):
73 73 user = user_util.create_user()
74 74 perm = 'repository.read'
75 75 id_, params = build_data(
76 76 self.apikey,
77 77 'grant_user_permission',
78 78 repoid=backend.repo_name,
79 79 userid=user.username,
80 80 perm=perm)
81 81 response = api_call(self.app, params)
82 82
83 83 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
84 84 user.username, backend.repo_name
85 85 )
86 86 assert_error(id_, expected, given=response.body)
@@ -1,156 +1,156 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGrantUserPermissionFromRepoGroup(object):
31 31 @pytest.mark.parametrize("name, perm, apply_to_children", [
32 32 ('none', 'group.none', 'none'),
33 33 ('read', 'group.read', 'none'),
34 34 ('write', 'group.write', 'none'),
35 35 ('admin', 'group.admin', 'none'),
36 36
37 37 ('none', 'group.none', 'all'),
38 38 ('read', 'group.read', 'all'),
39 39 ('write', 'group.write', 'all'),
40 40 ('admin', 'group.admin', 'all'),
41 41
42 42 ('none', 'group.none', 'repos'),
43 43 ('read', 'group.read', 'repos'),
44 44 ('write', 'group.write', 'repos'),
45 45 ('admin', 'group.admin', 'repos'),
46 46
47 47 ('none', 'group.none', 'groups'),
48 48 ('read', 'group.read', 'groups'),
49 49 ('write', 'group.write', 'groups'),
50 50 ('admin', 'group.admin', 'groups'),
51 51 ])
52 52 def test_api_grant_user_permission_to_repo_group(
53 53 self, name, perm, apply_to_children, user_util):
54 54 user = user_util.create_user()
55 55 repo_group = user_util.create_repo_group()
56 56 id_, params = build_data(
57 57 self.apikey, 'grant_user_permission_to_repo_group',
58 58 repogroupid=repo_group.name, userid=user.username,
59 59 perm=perm, apply_to_children=apply_to_children)
60 60 response = api_call(self.app, params)
61 61
62 62 ret = {
63 63 'msg': (
64 64 'Granted perm: `%s` (recursive:%s) for user: `%s`'
65 65 ' in repo group: `%s`' % (
66 66 perm, apply_to_children, user.username, repo_group.name
67 67 )
68 68 ),
69 69 'success': True
70 70 }
71 71 expected = ret
72 72 assert_ok(id_, expected, given=response.body)
73 73
74 74 @pytest.mark.parametrize(
75 75 "name, perm, apply_to_children, grant_admin, access_ok", [
76 76 ('none_fails', 'group.none', 'none', False, False),
77 77 ('read_fails', 'group.read', 'none', False, False),
78 78 ('write_fails', 'group.write', 'none', False, False),
79 79 ('admin_fails', 'group.admin', 'none', False, False),
80 80
81 81 # with granted perms
82 82 ('none_ok', 'group.none', 'none', True, True),
83 83 ('read_ok', 'group.read', 'none', True, True),
84 84 ('write_ok', 'group.write', 'none', True, True),
85 85 ('admin_ok', 'group.admin', 'none', True, True),
86 86 ]
87 87 )
88 88 def test_api_grant_user_permission_to_repo_group_by_regular_user(
89 89 self, name, perm, apply_to_children, grant_admin, access_ok,
90 90 user_util):
91 91 user = user_util.create_user()
92 92 repo_group = user_util.create_repo_group()
93 93
94 94 if grant_admin:
95 95 test_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
96 96 user_util.grant_user_permission_to_repo_group(
97 97 repo_group, test_user, 'group.admin')
98 98
99 99 id_, params = build_data(
100 100 self.apikey_regular, 'grant_user_permission_to_repo_group',
101 101 repogroupid=repo_group.name, userid=user.username,
102 102 perm=perm, apply_to_children=apply_to_children)
103 103 response = api_call(self.app, params)
104 104 if access_ok:
105 105 ret = {
106 106 'msg': (
107 107 'Granted perm: `%s` (recursive:%s) for user: `%s`'
108 108 ' in repo group: `%s`' % (
109 109 perm, apply_to_children, user.username, repo_group.name
110 110 )
111 111 ),
112 112 'success': True
113 113 }
114 114 expected = ret
115 115 assert_ok(id_, expected, given=response.body)
116 116 else:
117 117 expected = 'repository group `%s` does not exist' % (
118 118 repo_group.name, )
119 119 assert_error(id_, expected, given=response.body)
120 120
121 121 def test_api_grant_user_permission_to_repo_group_wrong_permission(
122 122 self, user_util):
123 123 user = user_util.create_user()
124 124 repo_group = user_util.create_repo_group()
125 125 perm = 'haha.no.permission'
126 126 id_, params = build_data(
127 127 self.apikey,
128 128 'grant_user_permission_to_repo_group',
129 129 repogroupid=repo_group.name,
130 130 userid=user.username,
131 131 perm=perm)
132 132 response = api_call(self.app, params)
133 133
134 134 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,)
135 135 assert_error(id_, expected, given=response.body)
136 136
137 137 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
138 138 def test_api_grant_user_permission_to_repo_group_exception_when_adding(
139 139 self, user_util):
140 140 user = user_util.create_user()
141 141 repo_group = user_util.create_repo_group()
142 142 perm = 'group.read'
143 143 id_, params = build_data(
144 144 self.apikey,
145 145 'grant_user_permission_to_repo_group',
146 146 repogroupid=repo_group.name,
147 147 userid=user.username,
148 148 perm=perm)
149 149 response = api_call(self.app, params)
150 150
151 151 expected = (
152 152 'failed to edit permission for user: `%s` in repo group: `%s`' % (
153 153 user.username, repo_group.name
154 154 )
155 155 )
156 156 assert_error(id_, expected, given=response.body)
@@ -1,155 +1,155 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.model.user_group import UserGroupModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestGrantUserPermissionFromUserGroup(object):
31 31 @pytest.mark.parametrize("name, perm", [
32 32 ('none', 'usergroup.none'),
33 33 ('read', 'usergroup.read'),
34 34 ('write', 'usergroup.write'),
35 35 ('admin', 'usergroup.admin'),
36 36
37 37 ('none', 'usergroup.none'),
38 38 ('read', 'usergroup.read'),
39 39 ('write', 'usergroup.write'),
40 40 ('admin', 'usergroup.admin'),
41 41
42 42 ('none', 'usergroup.none'),
43 43 ('read', 'usergroup.read'),
44 44 ('write', 'usergroup.write'),
45 45 ('admin', 'usergroup.admin'),
46 46
47 47 ('none', 'usergroup.none'),
48 48 ('read', 'usergroup.read'),
49 49 ('write', 'usergroup.write'),
50 50 ('admin', 'usergroup.admin'),
51 51 ])
52 52 def test_api_grant_user_permission_to_user_group(
53 53 self, name, perm, user_util):
54 54 user = user_util.create_user()
55 55 group = user_util.create_user_group()
56 56 id_, params = build_data(
57 57 self.apikey,
58 58 'grant_user_permission_to_user_group',
59 59 usergroupid=group.users_group_name,
60 60 userid=user.username,
61 61 perm=perm)
62 62 response = api_call(self.app, params)
63 63
64 64 ret = {
65 65 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
66 66 perm, user.username, group.users_group_name
67 67 ),
68 68 'success': True
69 69 }
70 70 expected = ret
71 71 assert_ok(id_, expected, given=response.body)
72 72
73 73 @pytest.mark.parametrize("name, perm, grant_admin, access_ok", [
74 74 ('none_fails', 'usergroup.none', False, False),
75 75 ('read_fails', 'usergroup.read', False, False),
76 76 ('write_fails', 'usergroup.write', False, False),
77 77 ('admin_fails', 'usergroup.admin', False, False),
78 78
79 79 # with granted perms
80 80 ('none_ok', 'usergroup.none', True, True),
81 81 ('read_ok', 'usergroup.read', True, True),
82 82 ('write_ok', 'usergroup.write', True, True),
83 83 ('admin_ok', 'usergroup.admin', True, True),
84 84 ])
85 85 def test_api_grant_user_permission_to_user_group_by_regular_user(
86 86 self, name, perm, grant_admin, access_ok, user_util):
87 87 api_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
88 88 user = user_util.create_user()
89 89 group = user_util.create_user_group()
90 90 # grant the user ability to at least read the group
91 91 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
92 92 user_util.grant_user_permission_to_user_group(
93 93 group, api_user, permission)
94 94
95 95 id_, params = build_data(
96 96 self.apikey_regular,
97 97 'grant_user_permission_to_user_group',
98 98 usergroupid=group.users_group_name,
99 99 userid=user.username,
100 100 perm=perm)
101 101 response = api_call(self.app, params)
102 102
103 103 if access_ok:
104 104 ret = {
105 105 'msg': (
106 106 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
107 107 perm, user.username, group.users_group_name
108 108 )
109 109 ),
110 110 'success': True
111 111 }
112 112 expected = ret
113 113 assert_ok(id_, expected, given=response.body)
114 114 else:
115 115 expected = 'user group `%s` does not exist' % (
116 116 group.users_group_name)
117 117 assert_error(id_, expected, given=response.body)
118 118
119 119 def test_api_grant_user_permission_to_user_group_wrong_permission(
120 120 self, user_util):
121 121 user = user_util.create_user()
122 122 group = user_util.create_user_group()
123 123 perm = 'haha.no.permission'
124 124 id_, params = build_data(
125 125 self.apikey,
126 126 'grant_user_permission_to_user_group',
127 127 usergroupid=group.users_group_name,
128 128 userid=user.username,
129 129 perm=perm)
130 130 response = api_call(self.app, params)
131 131
132 132 expected = 'permission `%s` does not exist. Permission should start with prefix: `usergroup.`' % perm
133 133 assert_error(id_, expected, given=response.body)
134 134
135 135 def test_api_grant_user_permission_to_user_group_exception_when_adding(
136 136 self, user_util):
137 137 user = user_util.create_user()
138 138 group = user_util.create_user_group()
139 139
140 140 perm = 'usergroup.read'
141 141 id_, params = build_data(
142 142 self.apikey,
143 143 'grant_user_permission_to_user_group',
144 144 usergroupid=group.users_group_name,
145 145 userid=user.username,
146 146 perm=perm)
147 147 with mock.patch.object(UserGroupModel, 'grant_user_permission', crash):
148 148 response = api_call(self.app, params)
149 149
150 150 expected = (
151 151 'failed to edit permission for user: `%s` in user group: `%s`' % (
152 152 user.username, group.users_group_name
153 153 )
154 154 )
155 155 assert_error(id_, expected, given=response.body)
@@ -1,67 +1,67 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.scm import ScmModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, assert_error, crash)
26 26 from rhodecode.model.repo import RepoModel
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestInvalidateCache(object):
31 31
32 32 def _set_cache(self, repo_name):
33 33 repo = RepoModel().get_by_repo_name(repo_name)
34 34 repo.scm_instance(cache=True)
35 35
36 36 def test_api_invalidate_cache(self, backend):
37 37 self._set_cache(backend.repo_name)
38 38
39 39 id_, params = build_data(
40 40 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
41 41 response = api_call(self.app, params)
42 42
43 43 expected = {
44 44 'msg': "Cache for repository `%s` was invalidated" % (
45 45 backend.repo_name,),
46 46 'repository': backend.repo_name,
47 47 }
48 48 assert_ok(id_, expected, given=response.body)
49 49
50 50 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
51 51 def test_api_invalidate_cache_error(self, backend):
52 52 id_, params = build_data(
53 53 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
54 54 response = api_call(self.app, params)
55 55
56 56 expected = 'Error occurred during cache invalidation action'
57 57 assert_error(id_, expected, given=response.body)
58 58
59 59 def test_api_invalidate_cache_regular_user_no_permission(self, backend):
60 60 self._set_cache(backend.repo_name)
61 61
62 62 id_, params = build_data(
63 63 self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name)
64 64 response = api_call(self.app, params)
65 65
66 66 expected = "repository `%s` does not exist" % (backend.repo_name,)
67 67 assert_error(id_, expected, given=response.body)
@@ -1,258 +1,258 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.db import UserLog, PullRequest
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestMergePullRequest(object):
31 31
32 32 @pytest.mark.backends("git", "hg")
33 33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 34 pull_request = pr_util.create_pull_request(mergeable=True)
35 35 pull_request_id = pull_request.pull_request_id
36 36 pull_request_repo = pull_request.target_repo.repo_name
37 37
38 38 id_, params = build_data(
39 39 self.apikey, 'merge_pull_request',
40 40 repoid=pull_request_repo,
41 41 pullrequestid=pull_request_id)
42 42
43 43 response = api_call(self.app, params)
44 44
45 45 # The above api call detaches the pull request DB object from the
46 46 # session because of an unconditional transaction rollback in our
47 47 # middleware. Therefore we need to add it back here if we want to use it.
48 48 Session().add(pull_request)
49 49
50 50 expected = 'merge not possible for following reasons: ' \
51 51 'Pull request reviewer approval is pending.'
52 52 assert_error(id_, expected, given=response.body)
53 53
54 54 @pytest.mark.backends("git", "hg")
55 55 def test_api_merge_pull_request_merge_failed_disallowed_state(
56 56 self, pr_util, no_notifications):
57 57 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
58 58 pull_request_id = pull_request.pull_request_id
59 59 pull_request_repo = pull_request.target_repo.repo_name
60 60
61 61 pr = PullRequest.get(pull_request_id)
62 62 pr.pull_request_state = pull_request.STATE_UPDATING
63 63 Session().add(pr)
64 64 Session().commit()
65 65
66 66 id_, params = build_data(
67 67 self.apikey, 'merge_pull_request',
68 68 repoid=pull_request_repo,
69 69 pullrequestid=pull_request_id)
70 70
71 71 response = api_call(self.app, params)
72 72 expected = 'Operation forbidden because pull request is in state {}, '\
73 73 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING,
74 74 PullRequest.STATE_CREATED)
75 75 assert_error(id_, expected, given=response.body)
76 76
77 77 @pytest.mark.backends("git", "hg")
78 78 def test_api_merge_pull_request(self, pr_util, no_notifications):
79 79 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
80 80 author = pull_request.user_id
81 81 repo = pull_request.target_repo.repo_id
82 82 pull_request_id = pull_request.pull_request_id
83 83 pull_request_repo = pull_request.target_repo.repo_name
84 84
85 85 id_, params = build_data(
86 86 self.apikey, 'comment_pull_request',
87 87 repoid=pull_request_repo,
88 88 pullrequestid=pull_request_id,
89 89 status='approved')
90 90
91 91 response = api_call(self.app, params)
92 92 expected = {
93 93 'comment_id': response.json.get('result', {}).get('comment_id'),
94 94 'pull_request_id': pull_request_id,
95 95 'status': {'given': 'approved', 'was_changed': True}
96 96 }
97 97 assert_ok(id_, expected, given=response.body)
98 98
99 99 id_, params = build_data(
100 100 self.apikey, 'merge_pull_request',
101 101 repoid=pull_request_repo,
102 102 pullrequestid=pull_request_id)
103 103
104 104 response = api_call(self.app, params)
105 105
106 106 pull_request = PullRequest.get(pull_request_id)
107 107
108 108 expected = {
109 109 'executed': True,
110 110 'failure_reason': 0,
111 111 'merge_status_message': 'This pull request can be automatically merged.',
112 112 'possible': True,
113 113 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
114 114 'merge_ref': pull_request.shadow_merge_ref.asdict()
115 115 }
116 116
117 117 assert_ok(id_, expected, response.body)
118 118
119 119 journal = UserLog.query()\
120 120 .filter(UserLog.user_id == author)\
121 121 .filter(UserLog.repository_id == repo) \
122 122 .order_by(UserLog.user_log_id.asc()) \
123 123 .all()
124 124 assert journal[-2].action == 'repo.pull_request.merge'
125 125 assert journal[-1].action == 'repo.pull_request.close'
126 126
127 127 id_, params = build_data(
128 128 self.apikey, 'merge_pull_request',
129 129 repoid=pull_request_repo, pullrequestid=pull_request_id)
130 130 response = api_call(self.app, params)
131 131
132 132 expected = 'merge not possible for following reasons: This pull request is closed.'
133 133 assert_error(id_, expected, given=response.body)
134 134
135 135 @pytest.mark.backends("git", "hg")
136 136 def test_api_merge_pull_request_as_another_user_no_perms_to_merge(
137 137 self, pr_util, no_notifications, user_util):
138 138 merge_user = user_util.create_user()
139 139 merge_user_id = merge_user.user_id
140 140 merge_user_username = merge_user.username
141 141
142 142 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
143 143
144 144 pull_request_id = pull_request.pull_request_id
145 145 pull_request_repo = pull_request.target_repo.repo_name
146 146
147 147 id_, params = build_data(
148 148 self.apikey, 'comment_pull_request',
149 149 repoid=pull_request_repo,
150 150 pullrequestid=pull_request_id,
151 151 status='approved')
152 152
153 153 response = api_call(self.app, params)
154 154 expected = {
155 155 'comment_id': response.json.get('result', {}).get('comment_id'),
156 156 'pull_request_id': pull_request_id,
157 157 'status': {'given': 'approved', 'was_changed': True}
158 158 }
159 159 assert_ok(id_, expected, given=response.body)
160 160 id_, params = build_data(
161 161 self.apikey, 'merge_pull_request',
162 162 repoid=pull_request_repo,
163 163 pullrequestid=pull_request_id,
164 164 userid=merge_user_id
165 165 )
166 166
167 167 response = api_call(self.app, params)
168 168 expected = 'merge not possible for following reasons: User `{}` ' \
169 169 'not allowed to perform merge.'.format(merge_user_username)
170 170 assert_error(id_, expected, response.body)
171 171
172 172 @pytest.mark.backends("git", "hg")
173 173 def test_api_merge_pull_request_as_another_user(self, pr_util, no_notifications, user_util):
174 174 merge_user = user_util.create_user()
175 175 merge_user_id = merge_user.user_id
176 176 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
177 177 user_util.grant_user_permission_to_repo(
178 178 pull_request.target_repo, merge_user, 'repository.write')
179 179 author = pull_request.user_id
180 180 repo = pull_request.target_repo.repo_id
181 181 pull_request_id = pull_request.pull_request_id
182 182 pull_request_repo = pull_request.target_repo.repo_name
183 183
184 184 id_, params = build_data(
185 185 self.apikey, 'comment_pull_request',
186 186 repoid=pull_request_repo,
187 187 pullrequestid=pull_request_id,
188 188 status='approved')
189 189
190 190 response = api_call(self.app, params)
191 191 expected = {
192 192 'comment_id': response.json.get('result', {}).get('comment_id'),
193 193 'pull_request_id': pull_request_id,
194 194 'status': {'given': 'approved', 'was_changed': True}
195 195 }
196 196 assert_ok(id_, expected, given=response.body)
197 197
198 198 id_, params = build_data(
199 199 self.apikey, 'merge_pull_request',
200 200 repoid=pull_request_repo,
201 201 pullrequestid=pull_request_id,
202 202 userid=merge_user_id
203 203 )
204 204
205 205 response = api_call(self.app, params)
206 206
207 207 pull_request = PullRequest.get(pull_request_id)
208 208
209 209 expected = {
210 210 'executed': True,
211 211 'failure_reason': 0,
212 212 'merge_status_message': 'This pull request can be automatically merged.',
213 213 'possible': True,
214 214 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
215 215 'merge_ref': pull_request.shadow_merge_ref.asdict()
216 216 }
217 217
218 218 assert_ok(id_, expected, response.body)
219 219
220 220 journal = UserLog.query() \
221 221 .filter(UserLog.user_id == merge_user_id) \
222 222 .filter(UserLog.repository_id == repo) \
223 223 .order_by(UserLog.user_log_id.asc()) \
224 224 .all()
225 225 assert journal[-2].action == 'repo.pull_request.merge'
226 226 assert journal[-1].action == 'repo.pull_request.close'
227 227
228 228 id_, params = build_data(
229 229 self.apikey, 'merge_pull_request',
230 230 repoid=pull_request_repo, pullrequestid=pull_request_id, userid=merge_user_id)
231 231 response = api_call(self.app, params)
232 232
233 233 expected = 'merge not possible for following reasons: This pull request is closed.'
234 234 assert_error(id_, expected, given=response.body)
235 235
236 236 @pytest.mark.backends("git", "hg")
237 237 def test_api_merge_pull_request_repo_error(self, pr_util):
238 238 pull_request = pr_util.create_pull_request()
239 239 id_, params = build_data(
240 240 self.apikey, 'merge_pull_request',
241 241 repoid=666, pullrequestid=pull_request.pull_request_id)
242 242 response = api_call(self.app, params)
243 243
244 244 expected = 'repository `666` does not exist'
245 245 assert_error(id_, expected, given=response.body)
246 246
247 247 @pytest.mark.backends("git", "hg")
248 248 def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util):
249 249 pull_request = pr_util.create_pull_request(mergeable=True)
250 250 id_, params = build_data(
251 251 self.apikey_regular, 'merge_pull_request',
252 252 repoid=pull_request.target_repo.repo_name,
253 253 pullrequestid=pull_request.pull_request_id,
254 254 userid=TEST_USER_ADMIN_LOGIN)
255 255 response = api_call(self.app, params)
256 256
257 257 expected = 'userid is not the same as your user'
258 258 assert_error(id_, expected, given=response.body)
@@ -1,54 +1,54 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.tests import TESTS_TMP_PATH
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestPull(object):
31 31
32 32 @pytest.mark.backends("git", "hg")
33 33 def test_api_pull(self, backend):
34 34 r = backend.create_repo()
35 35 repo_name = r.repo_name
36 36 clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
37 37 r.clone_uri = clone_uri
38 38
39 39 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
40 40 with mock.patch('rhodecode.model.scm.url_validator'):
41 41 response = api_call(self.app, params)
42 42 msg = 'Pulled from url `%s` on repo `%s`' % (
43 43 clone_uri, repo_name)
44 44 expected = {'msg': msg,
45 45 'repository': repo_name}
46 46 assert_ok(id_, expected, given=response.body)
47 47
48 48 def test_api_pull_error(self, backend):
49 49 id_, params = build_data(
50 50 self.apikey, 'pull', repoid=backend.repo_name)
51 51 response = api_call(self.app, params)
52 52
53 53 expected = 'Unable to pull changes from `None`'
54 54 assert_error(id_, expected, given=response.body)
@@ -1,65 +1,65 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.db import Repository, RepositoryField
23 23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 24
25 25
26 26 @pytest.mark.usefixtures("testuser_api", "app")
27 27 class TestRemoveFieldFromRepo(object):
28 28 def test_api_remove_field_from_repo(self, backend):
29 29 repo = backend.create_repo()
30 30 repo_name = repo.repo_name
31 31
32 32 id_, params = build_data(
33 33 self.apikey, 'add_field_to_repo',
34 34 repoid=repo_name,
35 35 key='extra_field',
36 36 label='extra_field_label',
37 37 description='extra_field_desc')
38 38 response = api_call(self.app, params)
39 39 expected = {
40 40 'msg': 'Added new repository field `extra_field`',
41 41 'success': True,
42 42 }
43 43 assert_ok(id_, expected, given=response.body)
44 44
45 45 repo = Repository.get_by_repo_name(repo_name)
46 46 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
47 47 _data = repo_field.get_dict()
48 48 assert _data['field_desc'] == 'extra_field_desc'
49 49 assert _data['field_key'] == 'extra_field'
50 50 assert _data['field_label'] == 'extra_field_label'
51 51
52 52 id_, params = build_data(
53 53 self.apikey, 'remove_field_from_repo',
54 54 repoid=repo_name,
55 55 key='extra_field')
56 56 response = api_call(self.app, params)
57 57 expected = {
58 58 'msg': 'Deleted repository field `extra_field`',
59 59 'success': True,
60 60 }
61 61 assert_ok(id_, expected, given=response.body)
62 62 repo = Repository.get_by_repo_name(repo_name)
63 63 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
64 64
65 65 assert repo_field is None
@@ -1,57 +1,57 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.user_group import UserGroupModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestRemoveUserFromUserGroup(object):
30 30 def test_api_remove_user_from_user_group(self, user_util):
31 31 user, group = user_util.create_user_with_group()
32 32 user_name = user.username
33 33 group_name = group.users_group_name
34 34 id_, params = build_data(
35 35 self.apikey, 'remove_user_from_user_group',
36 36 usergroupid=group_name,
37 37 userid=user.username)
38 38 response = api_call(self.app, params)
39 39
40 40 expected = {
41 41 'msg': 'removed member `%s` from user group `%s`' % (
42 42 user_name, group_name
43 43 ),
44 44 'success': True}
45 45 assert_ok(id_, expected, given=response.body)
46 46
47 47 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
48 48 def test_api_remove_user_from_user_group_exception_occurred(
49 49 self, user_util):
50 50 user, group = user_util.create_user_with_group()
51 51 id_, params = build_data(
52 52 self.apikey, 'remove_user_from_user_group',
53 53 usergroupid=group.users_group_name, userid=user.username)
54 54 response = api_call(self.app, params)
55 55 expected = 'failed to remove member from user group `%s`' % (
56 56 group.users_group_name)
57 57 assert_error(id_, expected, given=response.body)
@@ -1,183 +1,183 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import mock
22 22 import pytest
23 23
24 24 from rhodecode.model.db import Repository
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.lib.ext_json import json
27 27 from rhodecode.lib.utils2 import time_to_datetime
28 28 from rhodecode.api.tests.utils import (
29 29 build_data, api_call, assert_ok, assert_error, crash)
30 30 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
31 31
32 32
33 33 @pytest.mark.usefixtures("testuser_api", "app")
34 34 class TestLock(object):
35 35 def test_api_lock_repo_lock_aquire(self, backend):
36 36 id_, params = build_data(
37 37 self.apikey, 'lock',
38 38 userid=TEST_USER_ADMIN_LOGIN,
39 39 repoid=backend.repo_name,
40 40 locked=True)
41 41 response = api_call(self.app, params)
42 42 expected = {
43 43 'repo': backend.repo_name, 'locked': True,
44 44 'locked_since': response.json['result']['locked_since'],
45 45 'locked_by': TEST_USER_ADMIN_LOGIN,
46 46 'lock_state_changed': True,
47 47 'lock_reason': Repository.LOCK_API,
48 48 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
49 49 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
50 50 }
51 51 assert_ok(id_, expected, given=response.body)
52 52
53 53 def test_repo_lock_aquire_by_non_admin(self, backend):
54 54 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
55 55 repo_name = repo.repo_name
56 56 id_, params = build_data(
57 57 self.apikey_regular, 'lock',
58 58 repoid=repo_name,
59 59 locked=True)
60 60 response = api_call(self.app, params)
61 61 expected = {
62 62 'repo': repo_name,
63 63 'locked': True,
64 64 'locked_since': response.json['result']['locked_since'],
65 65 'locked_by': self.TEST_USER_LOGIN,
66 66 'lock_state_changed': True,
67 67 'lock_reason': Repository.LOCK_API,
68 68 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
69 69 % (self.TEST_USER_LOGIN, repo_name, True))
70 70 }
71 71 assert_ok(id_, expected, given=response.body)
72 72
73 73 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend):
74 74 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
75 75 repo_name = repo.repo_name
76 76 id_, params = build_data(
77 77 self.apikey_regular, 'lock',
78 78 userid=TEST_USER_ADMIN_LOGIN,
79 79 repoid=repo_name,
80 80 locked=True)
81 81 response = api_call(self.app, params)
82 82 expected = 'userid is not the same as your user'
83 83 assert_error(id_, expected, given=response.body)
84 84
85 85 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend):
86 86 id_, params = build_data(
87 87 self.apikey_regular, 'lock',
88 88 repoid=backend.repo_name,
89 89 locked=True)
90 90 response = api_call(self.app, params)
91 91 expected = 'repository `%s` does not exist' % (backend.repo_name, )
92 92 assert_error(id_, expected, given=response.body)
93 93
94 94 def test_api_lock_repo_lock_release(self, backend):
95 95 id_, params = build_data(
96 96 self.apikey, 'lock',
97 97 userid=TEST_USER_ADMIN_LOGIN,
98 98 repoid=backend.repo_name,
99 99 locked=False)
100 100 response = api_call(self.app, params)
101 101 expected = {
102 102 'repo': backend.repo_name,
103 103 'locked': False,
104 104 'locked_since': None,
105 105 'locked_by': TEST_USER_ADMIN_LOGIN,
106 106 'lock_state_changed': True,
107 107 'lock_reason': Repository.LOCK_API,
108 108 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
109 109 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False))
110 110 }
111 111 assert_ok(id_, expected, given=response.body)
112 112
113 113 def test_api_lock_repo_lock_aquire_optional_userid(self, backend):
114 114 id_, params = build_data(
115 115 self.apikey, 'lock',
116 116 repoid=backend.repo_name,
117 117 locked=True)
118 118 response = api_call(self.app, params)
119 119 time_ = response.json['result']['locked_since']
120 120 expected = {
121 121 'repo': backend.repo_name,
122 122 'locked': True,
123 123 'locked_since': time_,
124 124 'locked_by': TEST_USER_ADMIN_LOGIN,
125 125 'lock_state_changed': True,
126 126 'lock_reason': Repository.LOCK_API,
127 127 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
128 128 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
129 129 }
130 130
131 131 assert_ok(id_, expected, given=response.body)
132 132
133 133 def test_api_lock_repo_lock_optional_locked(self, backend):
134 134 # TODO: Provide a fixture locked_repository or similar
135 135 repo = Repository.get_by_repo_name(backend.repo_name)
136 136 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
137 137 Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API)
138 138
139 139 id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name)
140 140 response = api_call(self.app, params)
141 141 time_ = response.json['result']['locked_since']
142 142 expected = {
143 143 'repo': backend.repo_name,
144 144 'locked': True,
145 145 'locked_since': time_,
146 146 'locked_by': TEST_USER_ADMIN_LOGIN,
147 147 'lock_state_changed': False,
148 148 'lock_reason': Repository.LOCK_API,
149 149 'msg': ('Repo `%s` locked by `%s` on `%s`.'
150 150 % (backend.repo_name, TEST_USER_ADMIN_LOGIN,
151 151 json.dumps(time_to_datetime(time_))))
152 152 }
153 153 assert_ok(id_, expected, given=response.body)
154 154
155 155 def test_api_lock_repo_lock_optional_not_locked(self, backend):
156 156 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
157 157 repo_name = repo.repo_name
158 158 assert repo.locked == [None, None, None]
159 159 id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id)
160 160 response = api_call(self.app, params)
161 161 expected = {
162 162 'repo': repo_name,
163 163 'locked': False,
164 164 'locked_since': None,
165 165 'locked_by': None,
166 166 'lock_state_changed': False,
167 167 'lock_reason': None,
168 168 'msg': ('Repo `%s` not locked.' % (repo_name,))
169 169 }
170 170 assert_ok(id_, expected, given=response.body)
171 171
172 172 @mock.patch.object(Repository, 'lock', crash)
173 173 def test_api_lock_error(self, backend):
174 174 id_, params = build_data(
175 175 self.apikey, 'lock',
176 176 userid=TEST_USER_ADMIN_LOGIN,
177 177 repoid=backend.repo_name,
178 178 locked=True)
179 179 response = api_call(self.app, params)
180 180
181 181 expected = 'Error occurred locking repository `%s`' % (
182 182 backend.repo_name,)
183 183 assert_error(id_, expected, given=response.body)
@@ -1,43 +1,43 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.scm import ScmModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, assert_error, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestRescanRepos(object):
30 30 def test_api_rescan_repos(self):
31 31 id_, params = build_data(self.apikey, 'rescan_repos')
32 32 response = api_call(self.app, params)
33 33
34 34 expected = {'added': [], 'removed': []}
35 35 assert_ok(id_, expected, given=response.body)
36 36
37 37 @mock.patch.object(ScmModel, 'repo_scan', crash)
38 38 def test_api_rescann_error(self):
39 39 id_, params = build_data(self.apikey, 'rescan_repos', )
40 40 response = api_call(self.app, params)
41 41
42 42 expected = 'Error occurred during rescan repositories action'
43 43 assert_error(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.repo import RepoModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestRevokeUserGroupPermission(object):
30 30 def test_api_revoke_user_group_permission(self, backend, user_util):
31 31 repo = backend.create_repo()
32 32 user_group = user_util.create_user_group()
33 33 user_util.grant_user_group_permission_to_repo(
34 34 repo, user_group, 'repository.read')
35 35 id_, params = build_data(
36 36 self.apikey,
37 37 'revoke_user_group_permission',
38 38 repoid=backend.repo_name,
39 39 usergroupid=user_group.users_group_name)
40 40 response = api_call(self.app, params)
41 41
42 42 expected = {
43 43 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
44 44 user_group.users_group_name, backend.repo_name
45 45 ),
46 46 'success': True
47 47 }
48 48 assert_ok(id_, expected, given=response.body)
49 49
50 50 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
51 51 def test_api_revoke_user_group_permission_exception_when_adding(
52 52 self, backend, user_util):
53 53 user_group = user_util.create_user_group()
54 54 id_, params = build_data(
55 55 self.apikey,
56 56 'revoke_user_group_permission',
57 57 repoid=backend.repo_name,
58 58 usergroupid=user_group.users_group_name)
59 59 response = api_call(self.app, params)
60 60
61 61 expected = (
62 62 'failed to edit permission for user group: `%s` in repo: `%s`' % (
63 63 user_group.users_group_name, backend.repo_name
64 64 )
65 65 )
66 66 assert_error(id_, expected, given=response.body)
@@ -1,128 +1,128 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.repo_group import RepoGroupModel
24 24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok, crash)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestRevokeUserGroupPermissionFromRepoGroup(object):
31 31 @pytest.mark.parametrize("name, apply_to_children", [
32 32 ('none', 'none'),
33 33 ('all', 'all'),
34 34 ('repos', 'repos'),
35 35 ('groups', 'groups'),
36 36 ])
37 37 def test_api_revoke_user_group_permission_from_repo_group(
38 38 self, name, apply_to_children, user_util):
39 39 user_group = user_util.create_user_group()
40 40 repo_group = user_util.create_repo_group()
41 41 user_util.grant_user_group_permission_to_repo_group(
42 42 repo_group, user_group, 'group.read')
43 43
44 44 id_, params = build_data(
45 45 self.apikey, 'revoke_user_group_permission_from_repo_group',
46 46 repogroupid=repo_group.name,
47 47 usergroupid=user_group.users_group_name,
48 48 apply_to_children=apply_to_children,)
49 49 response = api_call(self.app, params)
50 50
51 51 expected = {
52 52 'msg': (
53 53 'Revoked perm (recursive:%s) for user group: `%s`'
54 54 ' in repo group: `%s`' % (
55 55 apply_to_children, user_group.users_group_name,
56 56 repo_group.name
57 57 )
58 58 ),
59 59 'success': True
60 60 }
61 61 assert_ok(id_, expected, given=response.body)
62 62
63 63 @pytest.mark.parametrize(
64 64 "name, apply_to_children, grant_admin, access_ok", [
65 65 ('none', 'none', False, False),
66 66 ('all', 'all', False, False),
67 67 ('repos', 'repos', False, False),
68 68 ('groups', 'groups', False, False),
69 69
70 70 # after granting admin rights
71 71 ('none', 'none', False, False),
72 72 ('all', 'all', False, False),
73 73 ('repos', 'repos', False, False),
74 74 ('groups', 'groups', False, False),
75 75 ]
76 76 )
77 77 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
78 78 self, name, apply_to_children, grant_admin, access_ok, user_util):
79 79 user_group = user_util.create_user_group()
80 80 repo_group = user_util.create_repo_group()
81 81 user_util.grant_user_group_permission_to_repo_group(
82 82 repo_group, user_group, 'group.read')
83 83
84 84 if grant_admin:
85 85 user_util.grant_user_permission_to_repo_group(
86 86 repo_group.name, self.TEST_USER_LOGIN, 'group.admin')
87 87
88 88 id_, params = build_data(
89 89 self.apikey_regular,
90 90 'revoke_user_group_permission_from_repo_group',
91 91 repogroupid=repo_group.name,
92 92 usergroupid=user_group.users_group_name,
93 93 apply_to_children=apply_to_children,)
94 94 response = api_call(self.app, params)
95 95 if access_ok:
96 96 expected = {
97 97 'msg': (
98 98 'Revoked perm (recursive:%s) for user group: `%s`'
99 99 ' in repo group: `%s`' % (
100 100 apply_to_children, TEST_USER_ADMIN_LOGIN,
101 101 repo_group.name
102 102 )
103 103 ),
104 104 'success': True
105 105 }
106 106 assert_ok(id_, expected, given=response.body)
107 107 else:
108 108 expected = 'repository group `%s` does not exist' % (
109 109 repo_group.name,)
110 110 assert_error(id_, expected, given=response.body)
111 111
112 112 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
113 113 def test_api_revoke_user_group_permission_from_repo_group_exception_on_add(
114 114 self, user_util):
115 115 user_group = user_util.create_user_group()
116 116 repo_group = user_util.create_repo_group()
117 117 id_, params = build_data(
118 118 self.apikey, 'revoke_user_group_permission_from_repo_group',
119 119 repogroupid=repo_group.name,
120 120 usergroupid=user_group.users_group_name)
121 121 response = api_call(self.app, params)
122 122
123 123 expected = (
124 124 'failed to edit permission for user group: `%s`'
125 125 ' in repo group: `%s`' % (
126 126 user_group.users_group_name, repo_group.name)
127 127 )
128 128 assert_error(id_, expected, given=response.body)
@@ -1,57 +1,57 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.model.user import UserModel
23 23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 24
25 25
26 26 @pytest.mark.usefixtures("testuser_api", "app")
27 27 class TestRevokeUserGroupPermissionFromUserGroup(object):
28 28 @pytest.mark.parametrize("name", [
29 29 ('none',),
30 30 ('all',),
31 31 ('repos',),
32 32 ('groups',),
33 33 ])
34 34 def test_api_revoke_user_group_permission_from_user_group(
35 35 self, name, user_util):
36 36 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
37 37 group = user_util.create_user_group()
38 38 source_group = user_util.create_user_group()
39 39
40 40 user_util.grant_user_permission_to_user_group(
41 41 group, user, 'usergroup.read')
42 42 user_util.grant_user_group_permission_to_user_group(
43 43 source_group, group, 'usergroup.read')
44 44
45 45 id_, params = build_data(
46 46 self.apikey, 'revoke_user_group_permission_from_user_group',
47 47 usergroupid=group.users_group_name,
48 48 sourceusergroupid=source_group.users_group_name)
49 49 response = api_call(self.app, params)
50 50
51 51 expected = {
52 52 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % (
53 53 source_group.users_group_name, group.users_group_name
54 54 ),
55 55 'success': True
56 56 }
57 57 assert_ok(id_, expected, given=response.body)
@@ -1,65 +1,65 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.repo import RepoModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestRevokeUserPermission(object):
30 30 def test_api_revoke_user_permission(self, backend, user_util):
31 31 repo = backend.create_repo()
32 32 user = user_util.create_user()
33 33 user_util.grant_user_permission_to_repo(
34 34 repo, user, 'repository.read')
35 35
36 36 id_, params = build_data(
37 37 self.apikey,
38 38 'revoke_user_permission',
39 39 repoid=repo.repo_name,
40 40 userid=user.username)
41 41 response = api_call(self.app, params)
42 42
43 43 expected = {
44 44 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
45 45 user.username, backend.repo_name
46 46 ),
47 47 'success': True
48 48 }
49 49 assert_ok(id_, expected, given=response.body)
50 50
51 51 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
52 52 def test_api_revoke_user_permission_exception_when_adding(
53 53 self, backend, user_util):
54 54 user = user_util.create_user()
55 55 id_, params = build_data(
56 56 self.apikey,
57 57 'revoke_user_permission',
58 58 repoid=backend.repo_name,
59 59 userid=user.username)
60 60 response = api_call(self.app, params)
61 61
62 62 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
63 63 user.username, backend.repo_name
64 64 )
65 65 assert_error(id_, expected, given=response.body)
@@ -1,125 +1,125 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.repo_group import RepoGroupModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestRevokeUserPermissionFromRepoGroup(object):
30 30 @pytest.mark.parametrize("name, apply_to_children", [
31 31 ('none', 'none'),
32 32 ('all', 'all'),
33 33 ('repos', 'repos'),
34 34 ('groups', 'groups'),
35 35 ])
36 36 def test_api_revoke_user_permission_from_repo_group(
37 37 self, name, apply_to_children, user_util):
38 38 user = user_util.create_user()
39 39 repo_group = user_util.create_repo_group()
40 40 user_util.grant_user_permission_to_repo_group(
41 41 repo_group, user, 'group.read')
42 42
43 43 id_, params = build_data(
44 44 self.apikey,
45 45 'revoke_user_permission_from_repo_group',
46 46 repogroupid=repo_group.name,
47 47 userid=user.username,
48 48 apply_to_children=apply_to_children,)
49 49 response = api_call(self.app, params)
50 50
51 51 expected = {
52 52 'msg': (
53 53 'Revoked perm (recursive:%s) for user: `%s`'
54 54 ' in repo group: `%s`' % (
55 55 apply_to_children, user.username, repo_group.name
56 56 )
57 57 ),
58 58 'success': True
59 59 }
60 60 assert_ok(id_, expected, given=response.body)
61 61
62 62 @pytest.mark.parametrize(
63 63 "name, apply_to_children, grant_admin, access_ok", [
64 64 ('none', 'none', False, False),
65 65 ('all', 'all', False, False),
66 66 ('repos', 'repos', False, False),
67 67 ('groups', 'groups', False, False),
68 68
69 69 # after granting admin rights
70 70 ('none', 'none', False, False),
71 71 ('all', 'all', False, False),
72 72 ('repos', 'repos', False, False),
73 73 ('groups', 'groups', False, False),
74 74 ]
75 75 )
76 76 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
77 77 self, name, apply_to_children, grant_admin, access_ok, user_util):
78 78 user = user_util.create_user()
79 79 repo_group = user_util.create_repo_group()
80 80 permission = 'group.admin' if grant_admin else 'group.read'
81 81 user_util.grant_user_permission_to_repo_group(
82 82 repo_group, user, permission)
83 83
84 84 id_, params = build_data(
85 85 self.apikey_regular,
86 86 'revoke_user_permission_from_repo_group',
87 87 repogroupid=repo_group.name,
88 88 userid=user.username,
89 89 apply_to_children=apply_to_children,)
90 90 response = api_call(self.app, params)
91 91 if access_ok:
92 92 expected = {
93 93 'msg': (
94 94 'Revoked perm (recursive:%s) for user: `%s`'
95 95 ' in repo group: `%s`' % (
96 96 apply_to_children, user.username, repo_group.name
97 97 )
98 98 ),
99 99 'success': True
100 100 }
101 101 assert_ok(id_, expected, given=response.body)
102 102 else:
103 103 expected = 'repository group `%s` does not exist' % (
104 104 repo_group.name)
105 105 assert_error(id_, expected, given=response.body)
106 106
107 107 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
108 108 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(
109 109 self, user_util):
110 110 user = user_util.create_user()
111 111 repo_group = user_util.create_repo_group()
112 112 id_, params = build_data(
113 113 self.apikey,
114 114 'revoke_user_permission_from_repo_group',
115 115 repogroupid=repo_group.name,
116 116 userid=user.username
117 117 )
118 118 response = api_call(self.app, params)
119 119
120 120 expected = (
121 121 'failed to edit permission for user: `%s` in repo group: `%s`' % (
122 122 user.username, repo_group.name
123 123 )
124 124 )
125 125 assert_error(id_, expected, given=response.body)
@@ -1,111 +1,111 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.user_group import UserGroupModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok, crash)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestRevokeUserPermissionFromUserGroup(object):
30 30 @pytest.mark.parametrize("name", [
31 31 ('none',),
32 32 ('all',),
33 33 ('repos',),
34 34 ('groups',),
35 35 ])
36 36 def test_api_revoke_user_permission_from_user_group(self, name, user_util):
37 37 user = user_util.create_user()
38 38 group = user_util.create_user_group()
39 39 user_util.grant_user_permission_to_user_group(
40 40 group, user, 'usergroup.admin')
41 41
42 42 id_, params = build_data(
43 43 self.apikey,
44 44 'revoke_user_permission_from_user_group',
45 45 usergroupid=group.users_group_name,
46 46 userid=user.username)
47 47 response = api_call(self.app, params)
48 48
49 49 expected = {
50 50 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
51 51 user.username, group.users_group_name
52 52 ),
53 53 'success': True
54 54 }
55 55 assert_ok(id_, expected, given=response.body)
56 56
57 57 @pytest.mark.parametrize("name, grant_admin, access_ok", [
58 58 ('none', False, False),
59 59 ('all', False, False),
60 60 ('repos', False, False),
61 61 ('groups', False, False),
62 62
63 63 # after granting admin rights
64 64 ('none', False, False),
65 65 ('all', False, False),
66 66 ('repos', False, False),
67 67 ('groups', False, False),
68 68 ])
69 69 def test_api_revoke_user_permission_from_user_group_by_regular_user(
70 70 self, name, grant_admin, access_ok, user_util):
71 71 user = user_util.create_user()
72 72 group = user_util.create_user_group()
73 73 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
74 74 user_util.grant_user_permission_to_user_group(group, user, permission)
75 75
76 76 id_, params = build_data(
77 77 self.apikey_regular,
78 78 'revoke_user_permission_from_user_group',
79 79 usergroupid=group.users_group_name,
80 80 userid=user.username)
81 81 response = api_call(self.app, params)
82 82 if access_ok:
83 83 expected = {
84 84 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
85 85 user.username, group.users_group_name
86 86 ),
87 87 'success': True
88 88 }
89 89 assert_ok(id_, expected, given=response.body)
90 90 else:
91 91 expected = 'user group `%s` does not exist' % (
92 92 group.users_group_name)
93 93 assert_error(id_, expected, given=response.body)
94 94
95 95 @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash)
96 96 def test_api_revoke_user_permission_from_user_group_exception_when_adding(
97 97 self, user_util):
98 98 user = user_util.create_user()
99 99 group = user_util.create_user_group()
100 100 id_, params = build_data(
101 101 self.apikey,
102 102 'revoke_user_permission_from_user_group',
103 103 usergroupid=group.users_group_name,
104 104 userid=user.username)
105 105 response = api_call(self.app, params)
106 106
107 107 expected = (
108 108 'failed to edit permission for user: `%s` in user group: `%s`' % (
109 109 user.username, group.users_group_name)
110 110 )
111 111 assert_error(id_, expected, given=response.body)
@@ -1,58 +1,58 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22
23 23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, assert_error
24 24
25 25
26 26 @pytest.mark.usefixtures("testuser_api", "app")
27 27 class TestStoreException(object):
28 28
29 29 def test_store_exception_invalid_json(self):
30 30 id_, params = build_data(self.apikey, 'store_exception',
31 31 exc_data_json='XXX,{')
32 32 response = api_call(self.app, params)
33 33
34 34 expected = 'Failed to parse JSON data from exc_data_json field. ' \
35 35 'Please make sure it contains a valid JSON.'
36 36 assert_error(id_, expected, given=response.body)
37 37
38 38 def test_store_exception_missing_json_params_json(self):
39 39 id_, params = build_data(self.apikey, 'store_exception',
40 40 exc_data_json='{"foo":"bar"}')
41 41 response = api_call(self.app, params)
42 42
43 43 expected = "Missing exc_traceback, or exc_type_name in " \
44 44 "exc_data_json field. Missing: 'exc_traceback'"
45 45 assert_error(id_, expected, given=response.body)
46 46
47 47 def test_store_exception(self):
48 48 id_, params = build_data(
49 49 self.apikey, 'store_exception',
50 50 exc_data_json='{"exc_traceback": "invalid", "exc_type_name":"ValueError"}')
51 51 response = api_call(self.app, params)
52 52 exc_id = response.json['result']['exc_id']
53 53
54 54 expected = {
55 55 'exc_id': exc_id,
56 56 'exc_url': 'http://example.com/_admin/settings/exceptions/{}'.format(exc_id)
57 57 }
58 58 assert_ok(id_, expected, given=response.body)
@@ -1,214 +1,214 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import pytest
21 21
22 22 from rhodecode.lib.vcs.nodes import FileNode
23 23 from rhodecode.model.db import User
24 24 from rhodecode.model.pull_request import PullRequestModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_ok, assert_error)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestUpdatePullRequest(object):
32 32
33 33 @pytest.mark.backends("git", "hg")
34 34 def test_api_update_pull_request_title_or_description(
35 35 self, pr_util, no_notifications):
36 36 pull_request = pr_util.create_pull_request()
37 37
38 38 id_, params = build_data(
39 39 self.apikey, 'update_pull_request',
40 40 repoid=pull_request.target_repo.repo_name,
41 41 pullrequestid=pull_request.pull_request_id,
42 42 title='New TITLE OF A PR',
43 43 description='New DESC OF A PR',
44 44 )
45 45 response = api_call(self.app, params)
46 46
47 47 expected = {
48 48 "msg": "Updated pull request `{}`".format(
49 49 pull_request.pull_request_id),
50 50 "pull_request": response.json['result']['pull_request'],
51 51 "updated_commits": {"added": [], "common": [], "removed": []},
52 52 "updated_reviewers": {"added": [], "removed": []},
53 53 "updated_observers": {"added": [], "removed": []},
54 54 }
55 55
56 56 response_json = response.json['result']
57 57 assert response_json == expected
58 58 pr = response_json['pull_request']
59 59 assert pr['title'] == 'New TITLE OF A PR'
60 60 assert pr['description'] == 'New DESC OF A PR'
61 61
62 62 @pytest.mark.backends("git", "hg")
63 63 def test_api_try_update_closed_pull_request(
64 64 self, pr_util, no_notifications):
65 65 pull_request = pr_util.create_pull_request()
66 66 PullRequestModel().close_pull_request(
67 67 pull_request, TEST_USER_ADMIN_LOGIN)
68 68
69 69 id_, params = build_data(
70 70 self.apikey, 'update_pull_request',
71 71 repoid=pull_request.target_repo.repo_name,
72 72 pullrequestid=pull_request.pull_request_id)
73 73 response = api_call(self.app, params)
74 74
75 75 expected = 'pull request `{}` update failed, pull request ' \
76 76 'is closed'.format(pull_request.pull_request_id)
77 77
78 78 assert_error(id_, expected, response.body)
79 79
80 80 @pytest.mark.backends("git", "hg")
81 81 def test_api_update_update_commits(self, pr_util, no_notifications):
82 82 commits = [
83 83 {'message': 'a'},
84 84 {'message': 'b', 'added': [FileNode(b'file_b', b'test_content\n')]},
85 85 {'message': 'c', 'added': [FileNode(b'file_c', b'test_content\n')]},
86 86 ]
87 87 pull_request = pr_util.create_pull_request(
88 88 commits=commits, target_head='a', source_head='b', revisions=['b'])
89 89 pr_util.update_source_repository(head='c')
90 90 repo = pull_request.source_repo.scm_instance()
91 91 commits = [x for x in repo.get_commits()]
92 92
93 93 added_commit_id = commits[-1].raw_id # c commit
94 94 common_commit_id = commits[1].raw_id # b commit is common ancestor
95 95 total_commits = [added_commit_id, common_commit_id]
96 96
97 97 id_, params = build_data(
98 98 self.apikey, 'update_pull_request',
99 99 repoid=pull_request.target_repo.repo_name,
100 100 pullrequestid=pull_request.pull_request_id,
101 101 update_commits=True
102 102 )
103 103 response = api_call(self.app, params)
104 104
105 105 expected = {
106 106 "msg": "Updated pull request `{}`".format(
107 107 pull_request.pull_request_id),
108 108 "pull_request": response.json['result']['pull_request'],
109 109 "updated_commits": {"added": [added_commit_id],
110 110 "common": [common_commit_id],
111 111 "total": total_commits,
112 112 "removed": []},
113 113 "updated_reviewers": {"added": [], "removed": []},
114 114 "updated_observers": {"added": [], "removed": []},
115 115 }
116 116
117 117 assert_ok(id_, expected, response.body)
118 118
119 119 @pytest.mark.backends("git", "hg")
120 120 def test_api_update_change_reviewers(
121 121 self, user_util, pr_util, no_notifications):
122 122 a = user_util.create_user()
123 123 b = user_util.create_user()
124 124 c = user_util.create_user()
125 125 new_reviewers = [
126 126 {'username': b.username, 'reasons': ['updated via API'],
127 127 'mandatory':False},
128 128 {'username': c.username, 'reasons': ['updated via API'],
129 129 'mandatory':False},
130 130 ]
131 131
132 132 added = [b.username, c.username]
133 133 removed = [a.username]
134 134
135 135 pull_request = pr_util.create_pull_request(
136 136 reviewers=[(a.username, ['added via API'], False, 'reviewer', [])])
137 137
138 138 id_, params = build_data(
139 139 self.apikey, 'update_pull_request',
140 140 repoid=pull_request.target_repo.repo_name,
141 141 pullrequestid=pull_request.pull_request_id,
142 142 reviewers=new_reviewers)
143 143 response = api_call(self.app, params)
144 144 expected = {
145 145 "msg": "Updated pull request `{}`".format(
146 146 pull_request.pull_request_id),
147 147 "pull_request": response.json['result']['pull_request'],
148 148 "updated_commits": {"added": [], "common": [], "removed": []},
149 149 "updated_reviewers": {"added": added, "removed": removed},
150 150 "updated_observers": {"added": [], "removed": []},
151 151 }
152 152
153 153 assert_ok(id_, expected, response.body)
154 154
155 155 @pytest.mark.backends("git", "hg")
156 156 def test_api_update_bad_user_in_reviewers(self, pr_util):
157 157 pull_request = pr_util.create_pull_request()
158 158
159 159 id_, params = build_data(
160 160 self.apikey, 'update_pull_request',
161 161 repoid=pull_request.target_repo.repo_name,
162 162 pullrequestid=pull_request.pull_request_id,
163 163 reviewers=[{'username': 'bad_name'}])
164 164 response = api_call(self.app, params)
165 165
166 166 expected = 'user `bad_name` does not exist'
167 167
168 168 assert_error(id_, expected, response.body)
169 169
170 170 @pytest.mark.backends("git", "hg")
171 171 def test_api_update_repo_error(self, pr_util):
172 172 pull_request = pr_util.create_pull_request()
173 173 id_, params = build_data(
174 174 self.apikey, 'update_pull_request',
175 175 repoid='fake',
176 176 pullrequestid=pull_request.pull_request_id,
177 177 reviewers=[{'username': 'bad_name'}])
178 178 response = api_call(self.app, params)
179 179
180 180 expected = 'repository `fake` does not exist'
181 181
182 182 response_json = response.json['error']
183 183 assert response_json == expected
184 184
185 185 @pytest.mark.backends("git", "hg")
186 186 def test_api_update_pull_request_error(self, pr_util):
187 187 pull_request = pr_util.create_pull_request()
188 188
189 189 id_, params = build_data(
190 190 self.apikey, 'update_pull_request',
191 191 repoid=pull_request.target_repo.repo_name,
192 192 pullrequestid=999999,
193 193 reviewers=[{'username': 'bad_name'}])
194 194 response = api_call(self.app, params)
195 195
196 196 expected = 'pull request `999999` does not exist'
197 197 assert_error(id_, expected, response.body)
198 198
199 199 @pytest.mark.backends("git", "hg")
200 200 def test_api_update_pull_request_no_perms_to_update(
201 201 self, user_util, pr_util):
202 202 user = user_util.create_user()
203 203 pull_request = pr_util.create_pull_request()
204 204
205 205 id_, params = build_data(
206 206 user.api_key, 'update_pull_request',
207 207 repoid=pull_request.target_repo.repo_name,
208 208 pullrequestid=pull_request.pull_request_id,)
209 209 response = api_call(self.app, params)
210 210
211 211 expected = ('pull request `%s` update failed, '
212 212 'no permission to update.') % pull_request.pull_request_id
213 213
214 214 assert_error(id_, expected, response.body)
@@ -1,209 +1,209 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.repo import RepoModel
24 24 from rhodecode.model.scm import ScmModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 28 from rhodecode.tests.fixture import Fixture
29 29 from rhodecode.tests.fixture_mods.fixture_utils import plain_http_host_only_stub
30 30
31 31 fixture = Fixture()
32 32
33 33 UPDATE_REPO_NAME = 'api_update_me'
34 34
35 35
36 36 class SAME_AS_UPDATES(object):
37 37 """ Constant used for tests below """
38 38
39 39
40 40 @pytest.mark.usefixtures("testuser_api", "app")
41 41 class TestApiUpdateRepo(object):
42 42
43 43 @pytest.mark.parametrize("updates, expected", [
44 44 ({'owner': TEST_USER_REGULAR_LOGIN},
45 45 SAME_AS_UPDATES),
46 46
47 47 ({'description': 'new description'},
48 48 SAME_AS_UPDATES),
49 49
50 50 ({'clone_uri': 'http://foo.com/repo'},
51 51 SAME_AS_UPDATES),
52 52
53 53 ({'clone_uri': None},
54 54 {'clone_uri': ''}),
55 55
56 56 ({'clone_uri': ''},
57 57 {'clone_uri': ''}),
58 58
59 59 ({'clone_uri': 'http://example.com/repo_pull'},
60 60 {'clone_uri': 'http://example.com/repo_pull'}),
61 61
62 62 ({'push_uri': ''},
63 63 {'push_uri': ''}),
64 64
65 65 ({'push_uri': 'http://example.com/repo_push'},
66 66 {'push_uri': 'http://example.com/repo_push'}),
67 67
68 68 ({'landing_rev': None}, # auto-updated based on type of repo
69 69 {'landing_rev': [None, None]}),
70 70
71 71 ({'enable_statistics': True},
72 72 SAME_AS_UPDATES),
73 73
74 74 ({'enable_locking': True},
75 75 SAME_AS_UPDATES),
76 76
77 77 ({'enable_downloads': True},
78 78 SAME_AS_UPDATES),
79 79
80 80 ({'repo_name': 'new_repo_name'},
81 81 {
82 82 'repo_name': 'new_repo_name',
83 83 'url': 'http://{}/new_repo_name'.format(plain_http_host_only_stub())
84 84 }),
85 85
86 86 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
87 87 '_group': 'test_group_for_update'},
88 88 {
89 89 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
90 90 'url': 'http://{}/test_group_for_update/{}'.format(
91 91 plain_http_host_only_stub(), UPDATE_REPO_NAME)
92 92 }),
93 93 ])
94 94 def test_api_update_repo(self, updates, expected, backend):
95 95 repo_name = UPDATE_REPO_NAME
96 96 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
97 97 if updates.get('_group'):
98 98 fixture.create_repo_group(updates['_group'])
99 99
100 100 if 'landing_rev' in updates:
101 101 default_landing_ref, _lbl = ScmModel.backend_landing_ref(backend.alias)
102 102 _type, _name = default_landing_ref.split(':')
103 103 updates['landing_rev'] = default_landing_ref
104 104 expected['landing_rev'] = [_type, _name]
105 105
106 106 expected_api_data = repo.get_api_data(include_secrets=True)
107 107 if expected is SAME_AS_UPDATES:
108 108 expected_api_data.update(updates)
109 109 else:
110 110 expected_api_data.update(expected)
111 111
112 112 id_, params = build_data(
113 113 self.apikey, 'update_repo', repoid=repo_name, **updates)
114 114
115 115 with mock.patch('rhodecode.model.validation_schema.validators.url_validator'):
116 116 response = api_call(self.app, params)
117 117
118 118 if updates.get('repo_name'):
119 119 repo_name = updates['repo_name']
120 120
121 121 try:
122 122 expected = {
123 123 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
124 124 'repository': jsonify(expected_api_data)
125 125 }
126 126 assert_ok(id_, expected, given=response.body)
127 127 finally:
128 128 fixture.destroy_repo(repo_name)
129 129 if updates.get('_group'):
130 130 fixture.destroy_repo_group(updates['_group'])
131 131
132 132 def test_api_update_repo_fork_of_field(self, backend):
133 133 master_repo = backend.create_repo()
134 134 repo = backend.create_repo()
135 135 updates = {
136 136 'fork_of': master_repo.repo_name,
137 137 'fork_of_id': master_repo.repo_id
138 138 }
139 139 expected_api_data = repo.get_api_data(include_secrets=True)
140 140 expected_api_data.update(updates)
141 141
142 142 id_, params = build_data(
143 143 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
144 144 response = api_call(self.app, params)
145 145 expected = {
146 146 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
147 147 'repository': jsonify(expected_api_data)
148 148 }
149 149 assert_ok(id_, expected, given=response.body)
150 150 result = response.json['result']['repository']
151 151 assert result['fork_of'] == master_repo.repo_name
152 152 assert result['fork_of_id'] == master_repo.repo_id
153 153
154 154 def test_api_update_repo_fork_of_not_found(self, backend):
155 155 master_repo_name = 'fake-parent-repo'
156 156 repo = backend.create_repo()
157 157 updates = {
158 158 'fork_of': master_repo_name
159 159 }
160 160 id_, params = build_data(
161 161 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
162 162 response = api_call(self.app, params)
163 163 expected = {
164 164 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
165 165 master_repo_name)}
166 166 assert_error(id_, expected, given=response.body)
167 167
168 168 def test_api_update_repo_with_repo_group_not_existing(self):
169 169 repo_name = 'admin_owned'
170 170 fake_repo_group = 'test_group_for_update'
171 171 fixture.create_repo(repo_name)
172 172 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
173 173 id_, params = build_data(
174 174 self.apikey, 'update_repo', repoid=repo_name, **updates)
175 175 response = api_call(self.app, params)
176 176 try:
177 177 expected = {
178 178 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
179 179 }
180 180 assert_error(id_, expected, given=response.body)
181 181 finally:
182 182 fixture.destroy_repo(repo_name)
183 183
184 184 def test_api_update_repo_regular_user_not_allowed(self):
185 185 repo_name = 'admin_owned'
186 186 fixture.create_repo(repo_name)
187 187 updates = {'active': False}
188 188 id_, params = build_data(
189 189 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
190 190 response = api_call(self.app, params)
191 191 try:
192 192 expected = 'repository `%s` does not exist' % (repo_name,)
193 193 assert_error(id_, expected, given=response.body)
194 194 finally:
195 195 fixture.destroy_repo(repo_name)
196 196
197 197 @mock.patch.object(RepoModel, 'update', crash)
198 198 def test_api_update_repo_exception_occurred(self, backend):
199 199 repo_name = UPDATE_REPO_NAME
200 200 fixture.create_repo(repo_name, repo_type=backend.alias)
201 201 id_, params = build_data(
202 202 self.apikey, 'update_repo', repoid=repo_name,
203 203 owner=TEST_USER_ADMIN_LOGIN,)
204 204 response = api_call(self.app, params)
205 205 try:
206 206 expected = 'failed to update repo `%s`' % (repo_name,)
207 207 assert_error(id_, expected, given=response.body)
208 208 finally:
209 209 fixture.destroy_repo(repo_name)
@@ -1,149 +1,149 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import os
21 21
22 22 import pytest
23 23
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestApiUpdateRepoGroup(object):
32 32
33 33 def test_update_group_name(self, user_util):
34 34 new_group_name = 'new-group'
35 35 initial_name = self._update(user_util, group_name=new_group_name)
36 36 assert RepoGroupModel()._get_repo_group(initial_name) is None
37 37 new_group = RepoGroupModel()._get_repo_group(new_group_name)
38 38 assert new_group is not None
39 39 assert new_group.full_path == new_group_name
40 40
41 41 def test_update_group_name_change_parent(self, user_util):
42 42
43 43 parent_group = user_util.create_repo_group()
44 44 parent_group_name = parent_group.name
45 45
46 46 expected_group_name = '{}/{}'.format(parent_group_name, 'new-group')
47 47 initial_name = self._update(user_util, group_name=expected_group_name)
48 48
49 49 repo_group = RepoGroupModel()._get_repo_group(expected_group_name)
50 50
51 51 assert repo_group is not None
52 52 assert repo_group.group_name == expected_group_name
53 53 assert repo_group.full_path == expected_group_name
54 54 assert RepoGroupModel()._get_repo_group(initial_name) is None
55 55
56 56 new_path = os.path.join(
57 57 RepoGroupModel().repos_path, *repo_group.full_path_splitted)
58 58 assert os.path.exists(new_path)
59 59
60 60 def test_update_enable_locking(self, user_util):
61 61 initial_name = self._update(user_util, enable_locking=True)
62 62 repo_group = RepoGroupModel()._get_repo_group(initial_name)
63 63 assert repo_group.enable_locking is True
64 64
65 65 def test_update_description(self, user_util):
66 66 description = 'New description'
67 67 initial_name = self._update(user_util, description=description)
68 68 repo_group = RepoGroupModel()._get_repo_group(initial_name)
69 69 assert repo_group.group_description == description
70 70
71 71 def test_update_owner(self, user_util):
72 72 owner = self.TEST_USER_LOGIN
73 73 initial_name = self._update(user_util, owner=owner)
74 74 repo_group = RepoGroupModel()._get_repo_group(initial_name)
75 75 assert repo_group.user.username == owner
76 76
77 77 def test_update_group_name_conflict_with_existing(self, user_util):
78 78 group_1 = user_util.create_repo_group()
79 79 group_2 = user_util.create_repo_group()
80 80 repo_group_name_1 = group_1.group_name
81 81 repo_group_name_2 = group_2.group_name
82 82
83 83 id_, params = build_data(
84 84 self.apikey, 'update_repo_group', repogroupid=repo_group_name_1,
85 85 group_name=repo_group_name_2)
86 86 response = api_call(self.app, params)
87 87 expected = {
88 88 'unique_repo_group_name':
89 89 'Repository group with name `{}` already exists'.format(
90 90 repo_group_name_2)}
91 91 assert_error(id_, expected, given=response.body)
92 92
93 93 def test_api_update_repo_group_by_regular_user_no_permission(self, user_util):
94 94 temp_user = user_util.create_user()
95 95 temp_user_api_key = temp_user.api_key
96 96 parent_group = user_util.create_repo_group()
97 97 repo_group_name = parent_group.group_name
98 98 id_, params = build_data(
99 99 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name)
100 100 response = api_call(self.app, params)
101 101 expected = 'repository group `%s` does not exist' % (repo_group_name,)
102 102 assert_error(id_, expected, given=response.body)
103 103
104 104 def test_api_update_repo_group_regular_user_no_root_write_permissions(
105 105 self, user_util):
106 106 temp_user = user_util.create_user()
107 107 temp_user_api_key = temp_user.api_key
108 108 parent_group = user_util.create_repo_group(owner=temp_user.username)
109 109 repo_group_name = parent_group.group_name
110 110
111 111 id_, params = build_data(
112 112 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name,
113 113 group_name='at-root-level')
114 114 response = api_call(self.app, params)
115 115 expected = {
116 116 'repo_group': 'You do not have the permission to store '
117 117 'repository groups in the root location.'}
118 118 assert_error(id_, expected, given=response.body)
119 119
120 120 def _update(self, user_util, **kwargs):
121 121 repo_group = user_util.create_repo_group()
122 122 initial_name = repo_group.name
123 123 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
124 124 user_util.grant_user_permission_to_repo_group(
125 125 repo_group, user, 'group.admin')
126 126
127 127 id_, params = build_data(
128 128 self.apikey, 'update_repo_group', repogroupid=initial_name,
129 129 **kwargs)
130 130 response = api_call(self.app, params)
131 131
132 132 repo_group = RepoGroupModel.cls.get(repo_group.group_id)
133 133
134 134 expected = {
135 135 'msg': 'updated repository group ID:{} {}'.format(
136 136 repo_group.group_id, repo_group.group_name),
137 137 'repo_group': {
138 138 'repositories': [],
139 139 'group_name': repo_group.group_name,
140 140 'group_description': repo_group.group_description,
141 141 'owner': repo_group.user.username,
142 142 'group_id': repo_group.group_id,
143 143 'parent_group': (
144 144 repo_group.parent_group.name
145 145 if repo_group.parent_group else None)
146 146 }
147 147 }
148 148 assert_ok(id_, expected, given=response.body)
149 149 return initial_name
@@ -1,120 +1,120 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.db import User
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_ok, assert_error, crash, jsonify)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestUpdateUser(object):
32 32 @pytest.mark.parametrize("name, expected", [
33 33 ('firstname', 'new_username'),
34 34 ('lastname', 'new_username'),
35 35 ('email', 'new_username'),
36 36 ('admin', True),
37 37 ('admin', False),
38 38 ('extern_type', 'ldap'),
39 39 ('extern_type', None),
40 40 ('extern_name', 'test'),
41 41 ('extern_name', None),
42 42 ('active', False),
43 43 ('active', True),
44 44 ('password', 'newpass'),
45 45 ('description', 'CTO 4 Life')
46 46 ])
47 47 def test_api_update_user(self, name, expected, user_util):
48 48 usr = user_util.create_user()
49 49
50 50 kw = {name: expected, 'userid': usr.user_id}
51 51 id_, params = build_data(self.apikey, 'update_user', **kw)
52 52 response = api_call(self.app, params)
53 53
54 54 ret = {
55 55 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
56 56 'user': jsonify(
57 57 UserModel()
58 58 .get_by_username(usr.username)
59 59 .get_api_data(include_secrets=True)
60 60 )
61 61 }
62 62
63 63 expected = ret
64 64 assert_ok(id_, expected, given=response.body)
65 65
66 66 def test_api_update_user_no_changed_params(self):
67 67 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
68 68 ret = jsonify(usr.get_api_data(include_secrets=True))
69 69 id_, params = build_data(
70 70 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
71 71
72 72 response = api_call(self.app, params)
73 73 ret = {
74 74 'msg': 'updated user ID:%s %s' % (
75 75 usr.user_id, TEST_USER_ADMIN_LOGIN),
76 76 'user': ret
77 77 }
78 78 expected = ret
79 79 expected['user']['last_activity'] = response.json['result']['user'][
80 80 'last_activity']
81 81 assert_ok(id_, expected, given=response.body)
82 82
83 83 def test_api_update_user_by_user_id(self):
84 84 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
85 85 ret = jsonify(usr.get_api_data(include_secrets=True))
86 86 id_, params = build_data(
87 87 self.apikey, 'update_user', userid=usr.user_id)
88 88
89 89 response = api_call(self.app, params)
90 90 ret = {
91 91 'msg': 'updated user ID:%s %s' % (
92 92 usr.user_id, TEST_USER_ADMIN_LOGIN),
93 93 'user': ret
94 94 }
95 95 expected = ret
96 96 expected['user']['last_activity'] = response.json['result']['user'][
97 97 'last_activity']
98 98 assert_ok(id_, expected, given=response.body)
99 99
100 100 def test_api_update_user_default_user(self):
101 101 usr = User.get_default_user()
102 102 id_, params = build_data(
103 103 self.apikey, 'update_user', userid=usr.user_id)
104 104
105 105 response = api_call(self.app, params)
106 106 expected = 'editing default user is forbidden'
107 107 assert_error(id_, expected, given=response.body)
108 108
109 109 @mock.patch.object(UserModel, 'update_user', crash)
110 110 def test_api_update_user_when_exception_happens(self):
111 111 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
112 112 ret = jsonify(usr.get_api_data(include_secrets=True))
113 113 id_, params = build_data(
114 114 self.apikey, 'update_user', userid=usr.user_id)
115 115
116 116 response = api_call(self.app, params)
117 117 ret = 'failed to update user `%s`' % (usr.user_id,)
118 118
119 119 expected = ret
120 120 assert_error(id_, expected, given=response.body)
@@ -1,124 +1,124 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import mock
21 21 import pytest
22 22
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.model.user_group import UserGroupModel
25 25 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestUpdateUserGroup(object):
32 32 @pytest.mark.parametrize("changing_attr, updates", [
33 33 ('group_name', {'group_name': 'new_group_name'}),
34 34 ('group_name', {'group_name': 'test_group_for_update'}),
35 35 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
36 36 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
37 37 ('active', {'active': False}),
38 38 ('active', {'active': True}),
39 39 ('sync', {'sync': False}),
40 40 ('sync', {'sync': True})
41 41 ])
42 42 def test_api_update_user_group(self, changing_attr, updates, user_util):
43 43 user_group = user_util.create_user_group()
44 44 group_name = user_group.users_group_name
45 45 expected_api_data = user_group.get_api_data()
46 46 expected_api_data.update(updates)
47 47
48 48 id_, params = build_data(
49 49 self.apikey, 'update_user_group', usergroupid=group_name,
50 50 **updates)
51 51 response = api_call(self.app, params)
52 52
53 53 # special case for sync
54 54 if changing_attr == 'sync' and updates['sync'] is False:
55 55 expected_api_data['sync'] = None
56 56 elif changing_attr == 'sync' and updates['sync'] is True:
57 57 expected_api_data['sync'] = 'manual_api'
58 58
59 59 expected = {
60 60 'msg': 'updated user group ID:%s %s' % (
61 61 user_group.users_group_id, user_group.users_group_name),
62 62 'user_group': jsonify(expected_api_data)
63 63 }
64 64 assert_ok(id_, expected, given=response.body)
65 65
66 66 @pytest.mark.parametrize("changing_attr, updates", [
67 67 # TODO: mikhail: decide if we need to test against the commented params
68 68 # ('group_name', {'group_name': 'new_group_name'}),
69 69 # ('group_name', {'group_name': 'test_group_for_update'}),
70 70 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
71 71 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
72 72 ('active', {'active': False}),
73 73 ('active', {'active': True}),
74 74 ('sync', {'sync': False}),
75 75 ('sync', {'sync': True})
76 76 ])
77 77 def test_api_update_user_group_regular_user(
78 78 self, changing_attr, updates, user_util):
79 79 user_group = user_util.create_user_group()
80 80 group_name = user_group.users_group_name
81 81 expected_api_data = user_group.get_api_data()
82 82 expected_api_data.update(updates)
83 83
84 84 # grant permission to this user
85 85 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
86 86
87 87 user_util.grant_user_permission_to_user_group(
88 88 user_group, user, 'usergroup.admin')
89 89 id_, params = build_data(
90 90 self.apikey_regular, 'update_user_group',
91 91 usergroupid=group_name, **updates)
92 92 response = api_call(self.app, params)
93 93 # special case for sync
94 94 if changing_attr == 'sync' and updates['sync'] is False:
95 95 expected_api_data['sync'] = None
96 96 elif changing_attr == 'sync' and updates['sync'] is True:
97 97 expected_api_data['sync'] = 'manual_api'
98 98
99 99 expected = {
100 100 'msg': 'updated user group ID:%s %s' % (
101 101 user_group.users_group_id, user_group.users_group_name),
102 102 'user_group': jsonify(expected_api_data)
103 103 }
104 104 assert_ok(id_, expected, given=response.body)
105 105
106 106 def test_api_update_user_group_regular_user_no_permission(self, user_util):
107 107 user_group = user_util.create_user_group()
108 108 group_name = user_group.users_group_name
109 109 id_, params = build_data(
110 110 self.apikey_regular, 'update_user_group', usergroupid=group_name)
111 111 response = api_call(self.app, params)
112 112
113 113 expected = 'user group `%s` does not exist' % (group_name)
114 114 assert_error(id_, expected, given=response.body)
115 115
116 116 @mock.patch.object(UserGroupModel, 'update', crash)
117 117 def test_api_update_user_group_exception_occurred(self, user_util):
118 118 user_group = user_util.create_user_group()
119 119 group_name = user_group.users_group_name
120 120 id_, params = build_data(
121 121 self.apikey, 'update_user_group', usergroupid=group_name)
122 122 response = api_call(self.app, params)
123 123 expected = 'failed to update user group `%s`' % (group_name,)
124 124 assert_error(id_, expected, given=response.body)
@@ -1,297 +1,297 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import pytest
22 22 from mock import Mock, patch
23 23
24 24 from rhodecode.api import utils
25 25 from rhodecode.api import JSONRPCError
26 26 from rhodecode.lib.vcs.exceptions import RepositoryError
27 27
28 28
29 29 class TestGetCommitOrError(object):
30 30
31 31 def setup_method(self):
32 32 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
33 33
34 34 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d', 'branch:name'])
35 35 def test_ref_cannot_be_parsed(self, ref):
36 36 repo = Mock()
37 37 with pytest.raises(JSONRPCError) as excinfo:
38 38 utils.get_commit_or_error(ref, repo)
39 39 expected_message = (
40 40 'Ref `{ref}` given in a wrong format. Please check the API'
41 41 ' documentation for more details'.format(ref=ref)
42 42 )
43 43 assert excinfo.value.message == expected_message
44 44
45 45 def test_success_with_hash_specified(self):
46 46 repo = Mock()
47 47 ref_type = 'branch'
48 48 ref = '{}:master:{}'.format(ref_type, self.commit_hash)
49 49
50 50 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
51 51 result = utils.get_commit_or_error(ref, repo)
52 52 get_commit.assert_called_once_with(
53 53 repo, self.commit_hash)
54 54 assert result == get_commit()
55 55
56 56 def test_raises_an_error_when_commit_not_found(self):
57 57 repo = Mock()
58 58 ref = 'branch:master:{}'.format(self.commit_hash)
59 59
60 60 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
61 61 get_commit.side_effect = RepositoryError('Commit not found')
62 62 with pytest.raises(JSONRPCError) as excinfo:
63 63 utils.get_commit_or_error(ref, repo)
64 64 expected_message = 'Ref `{}` does not exist'.format(ref)
65 65 assert excinfo.value.message == expected_message
66 66
67 67
68 68 class TestResolveRefOrError(object):
69 69
70 70 def setup_method(self):
71 71 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
72 72
73 73 def test_success_with_no_hash_specified(self):
74 74 repo = Mock()
75 75 ref_type = 'branch'
76 76 ref_name = 'master'
77 77 ref = '{}:{}'.format(ref_type, ref_name)
78 78
79 79 with patch('rhodecode.api.utils._get_ref_hash') \
80 80 as _get_ref_hash:
81 81 _get_ref_hash.return_value = self.commit_hash
82 82 result = utils.resolve_ref_or_error(ref, repo)
83 83 _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name)
84 84 assert result == '{}:{}'.format(ref, self.commit_hash)
85 85
86 86 def test_non_supported_refs(self):
87 87 repo = Mock()
88 88 ref = 'bookmark:ref'
89 89 with pytest.raises(JSONRPCError) as excinfo:
90 90 utils.resolve_ref_or_error(ref, repo)
91 91 expected_message = (
92 92 'The specified value:bookmark:`ref` does not exist, or is not allowed.')
93 93 assert excinfo.value.message == expected_message
94 94
95 95 def test_branch_is_not_found(self):
96 96 repo = Mock()
97 97 ref = 'branch:non-existing-one'
98 98 with patch('rhodecode.api.utils._get_ref_hash')\
99 99 as _get_ref_hash:
100 100 _get_ref_hash.side_effect = KeyError()
101 101 with pytest.raises(JSONRPCError) as excinfo:
102 102 utils.resolve_ref_or_error(ref, repo)
103 103 expected_message = (
104 104 'The specified value:branch:`non-existing-one` does not exist, or is not allowed.')
105 105 assert excinfo.value.message == expected_message
106 106
107 107 def test_bookmark_is_not_found(self):
108 108 repo = Mock()
109 109 ref = 'bookmark:non-existing-one'
110 110 with patch('rhodecode.api.utils._get_ref_hash')\
111 111 as _get_ref_hash:
112 112 _get_ref_hash.side_effect = KeyError()
113 113 with pytest.raises(JSONRPCError) as excinfo:
114 114 utils.resolve_ref_or_error(ref, repo)
115 115 expected_message = (
116 116 'The specified value:bookmark:`non-existing-one` does not exist, or is not allowed.')
117 117 assert excinfo.value.message == expected_message
118 118
119 119 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
120 120 def test_ref_cannot_be_parsed(self, ref):
121 121 repo = Mock()
122 122 with pytest.raises(JSONRPCError) as excinfo:
123 123 utils.resolve_ref_or_error(ref, repo)
124 124 expected_message = (
125 125 'Ref `{ref}` given in a wrong format. Please check the API'
126 126 ' documentation for more details'.format(ref=ref)
127 127 )
128 128 assert excinfo.value.message == expected_message
129 129
130 130
131 131 class TestGetRefHash(object):
132 132
133 133 def setup_method(self):
134 134 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
135 135 self.bookmark_name = 'test-bookmark'
136 136
137 137 @pytest.mark.parametrize("alias, branch_name", [
138 138 ("git", "master"),
139 139 ("hg", "default")
140 140 ])
141 141 def test_returns_hash_by_branch_name(self, alias, branch_name):
142 142 with patch('rhodecode.model.db.Repository') as repo:
143 143 repo.scm_instance().alias = alias
144 144 repo.scm_instance().branches = {branch_name: self.commit_hash}
145 145 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
146 146 assert result_hash == self.commit_hash
147 147
148 148 @pytest.mark.parametrize("alias, branch_name", [
149 149 ("git", "master"),
150 150 ("hg", "default")
151 151 ])
152 152 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
153 153 with patch('rhodecode.model.db.Repository') as repo:
154 154 repo.scm_instance().alias = alias
155 155 repo.scm_instance().branches = {}
156 156 with pytest.raises(KeyError):
157 157 utils._get_ref_hash(repo, 'branch', branch_name)
158 158
159 159 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
160 160 with patch('rhodecode.model.db.Repository') as repo:
161 161 repo.scm_instance().alias = 'hg'
162 162 repo.scm_instance().bookmarks = {
163 163 self.bookmark_name: self.commit_hash}
164 164 result_hash = utils._get_ref_hash(
165 165 repo, 'bookmark', self.bookmark_name)
166 166 assert result_hash == self.commit_hash
167 167
168 168 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
169 169 with patch('rhodecode.model.db.Repository') as repo:
170 170 repo.scm_instance().alias = 'hg'
171 171 repo.scm_instance().bookmarks = {}
172 172 with pytest.raises(KeyError):
173 173 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
174 174
175 175 def test_raises_error_when_bookmark_is_specified_for_git(self):
176 176 with patch('rhodecode.model.db.Repository') as repo:
177 177 repo.scm_instance().alias = 'git'
178 178 repo.scm_instance().bookmarks = {
179 179 self.bookmark_name: self.commit_hash}
180 180 with pytest.raises(ValueError):
181 181 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
182 182
183 183
184 184 class TestUserByNameOrError(object):
185 185 def test_user_found_by_id(self):
186 186 fake_user = Mock(id=123)
187 187
188 188 patcher = patch('rhodecode.model.user.UserModel.get_user')
189 189 with patcher as get_user:
190 190 get_user.return_value = fake_user
191 191
192 192 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
193 193 with patcher as get_by_username:
194 194 result = utils.get_user_or_error(123)
195 195 assert result == fake_user
196 196
197 197 def test_user_not_found_by_id_as_str(self):
198 198 fake_user = Mock(id=123)
199 199
200 200 patcher = patch('rhodecode.model.user.UserModel.get_user')
201 201 with patcher as get_user:
202 202 get_user.return_value = fake_user
203 203 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
204 204 with patcher as get_by_username:
205 205 get_by_username.return_value = None
206 206
207 207 with pytest.raises(JSONRPCError):
208 208 utils.get_user_or_error('123')
209 209
210 210 def test_user_found_by_name(self):
211 211 fake_user = Mock(id=123)
212 212
213 213 patcher = patch('rhodecode.model.user.UserModel.get_user')
214 214 with patcher as get_user:
215 215 get_user.return_value = None
216 216
217 217 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
218 218 with patcher as get_by_username:
219 219 get_by_username.return_value = fake_user
220 220
221 221 result = utils.get_user_or_error('test')
222 222 assert result == fake_user
223 223
224 224 def test_user_not_found_by_id(self):
225 225 patcher = patch('rhodecode.model.user.UserModel.get_user')
226 226 with patcher as get_user:
227 227 get_user.return_value = None
228 228 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
229 229 with patcher as get_by_username:
230 230 get_by_username.return_value = None
231 231
232 232 with pytest.raises(JSONRPCError) as excinfo:
233 233 utils.get_user_or_error(123)
234 234
235 235 expected_message = 'user `123` does not exist'
236 236 assert excinfo.value.message == expected_message
237 237
238 238 def test_user_not_found_by_name(self):
239 239 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
240 240 with patcher as get_by_username:
241 241 get_by_username.return_value = None
242 242 with pytest.raises(JSONRPCError) as excinfo:
243 243 utils.get_user_or_error('test')
244 244
245 245 expected_message = 'user `test` does not exist'
246 246 assert excinfo.value.message == expected_message
247 247
248 248
249 249 class TestGetCommitDict(object):
250 250 @pytest.mark.parametrize('filename, expected', [
251 251 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
252 252 (b'sp\xa4cial', u'sp\ufffdcial'),
253 253 ])
254 254 def test_decodes_filenames_to_unicode(self, filename, expected):
255 255 result = utils._get_commit_dict(filename=filename, op='A')
256 256 assert result['filename'] == expected
257 257
258 258
259 259 class TestRepoAccess(object):
260 260 def setup_method(self, method):
261 261
262 262 self.admin_perm_patch = patch(
263 263 'rhodecode.api.utils.HasPermissionAnyApi')
264 264 self.repo_perm_patch = patch(
265 265 'rhodecode.api.utils.HasRepoPermissionAnyApi')
266 266
267 267 def test_has_superadmin_permission_checks_for_admin(self):
268 268 admin_mock = Mock()
269 269 with self.admin_perm_patch as amock:
270 270 amock.return_value = admin_mock
271 271 assert utils.has_superadmin_permission('fake_user')
272 272 amock.assert_called_once_with('hg.admin')
273 273
274 274 admin_mock.assert_called_once_with(user='fake_user')
275 275
276 276 def test_has_repo_permissions_checks_for_repo_access(self):
277 277 repo_mock = Mock()
278 278 fake_repo = Mock()
279 279 with self.repo_perm_patch as rmock:
280 280 rmock.return_value = repo_mock
281 281 assert utils.validate_repo_permissions(
282 282 'fake_user', 'fake_repo_id', fake_repo,
283 283 ['perm1', 'perm2'])
284 284 rmock.assert_called_once_with(*['perm1', 'perm2'])
285 285
286 286 repo_mock.assert_called_once_with(
287 287 user='fake_user', repo_name=fake_repo.repo_name)
288 288
289 289 def test_has_repo_permissions_raises_not_found(self):
290 290 repo_mock = Mock(return_value=False)
291 291 fake_repo = Mock()
292 292 with self.repo_perm_patch as rmock:
293 293 rmock.return_value = repo_mock
294 294 with pytest.raises(JSONRPCError) as excinfo:
295 295 utils.validate_repo_permissions(
296 296 'fake_user', 'fake_repo_id', fake_repo, 'perms')
297 297 assert 'fake_repo_id' in excinfo
@@ -1,123 +1,123 b''
1 1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20
21 21 import random
22 22 import pytest
23 23
24 24 from rhodecode.api.utils import get_origin
25 25 from rhodecode.lib.ext_json import json
26 26
27 27
28 28 def jsonify(obj):
29 29 return json.loads(json.dumps(obj))
30 30
31 31
32 32 API_URL = '/_admin/api'
33 33
34 34
35 35 def assert_call_ok(id_, given):
36 36 expected = jsonify({
37 37 'id': id_,
38 38 'error': None,
39 39 'result': None
40 40 })
41 41 given = json.loads(given)
42 42
43 43 assert expected['id'] == given['id']
44 44 assert expected['error'] == given['error']
45 45 return given['result']
46 46
47 47
48 48 def assert_ok(id_, expected, given):
49 49 given = json.loads(given)
50 50 if given.get('error'):
51 51 err = given['error']
52 52 pytest.fail(f"Unexpected ERROR in expected success response: `{err}`")
53 53
54 54 expected = jsonify({
55 55 'id': id_,
56 56 'error': None,
57 57 'result': expected
58 58 })
59 59
60 60 assert expected == given
61 61
62 62
63 63 def assert_error(id_, expected, given):
64 64 expected = jsonify({
65 65 'id': id_,
66 66 'error': expected,
67 67 'result': None
68 68 })
69 69 given = json.loads(given)
70 70 assert expected == given
71 71
72 72
73 73 def build_data(apikey, method, **kw):
74 74 """
75 75 Builds API data with given random ID
76 76 """
77 77 random_id = random.randrange(1, 9999)
78 78 return random_id, json.dumps({
79 79 "id": random_id,
80 80 "api_key": apikey,
81 81 "method": method,
82 82 "args": kw
83 83 })
84 84
85 85
86 86 def api_call(app, params, status=None):
87 87 response = app.post(
88 88 API_URL, content_type='application/json', params=params, status=status,
89 89 headers=[('Content-Type', 'application/json')])
90 90 return response
91 91
92 92
93 93 def crash(*args, **kwargs):
94 94 raise Exception('Total Crash !')
95 95
96 96
97 97 def expected_permissions(object_with_permissions):
98 98 """
99 99 Returns the expected permissions structure for the given object.
100 100
101 101 The object is expected to be a `Repository`, `RepositoryGroup`,
102 102 or `UserGroup`. They all implement the same permission handling
103 103 API.
104 104 """
105 105 permissions = []
106 106 for _user in object_with_permissions.permissions():
107 107 user_data = {
108 108 'name': _user.username,
109 109 'permission': _user.permission,
110 110 'origin': get_origin(_user),
111 111 'type': "user",
112 112 }
113 113 permissions.append(user_data)
114 114
115 115 for _user_group in object_with_permissions.permission_user_groups():
116 116 user_group_data = {
117 117 'name': _user_group.users_group_name,
118 118 'permission': _user_group.permission,
119 119 'origin': get_origin(_user_group),
120 120 'type': "user_group",
121 121 }
122 122 permissions.append(user_group_data)
123 123 return permissions
@@ -1,458 +1,458 b''
1 1
2 2
3 # Copyright (C) 2014-2020 RhodeCode GmbH
3 # Copyright (C) 2014-2023 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 JSON RPC utils
23 23 """
24 24
25 25 import collections
26 26 import logging
27 27
28 28 from rhodecode.api.exc import JSONRPCError
29 29 from rhodecode.lib.auth import (
30 30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
31 31 from rhodecode.lib.str_utils import safe_str
32 32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 33 from rhodecode.lib.view_utils import get_commit_from_ref_name
34 34 from rhodecode.lib.utils2 import str2bool
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38
39 39 class OAttr(object):
40 40 """
41 41 Special Option that defines other attribute, and can default to them
42 42
43 43 Example::
44 44
45 45 def test(apiuser, userid=Optional(OAttr('apiuser')):
46 46 user = Optional.extract(userid, evaluate_locals=local())
47 47 #if we pass in userid, we get it, else it will default to apiuser
48 48 #attribute
49 49 """
50 50
51 51 def __init__(self, attr_name):
52 52 self.attr_name = attr_name
53 53
54 54 def __repr__(self):
55 55 return '<OptionalAttr:%s>' % self.attr_name
56 56
57 57 def __call__(self):
58 58 return self
59 59
60 60
61 61 class Optional(object):
62 62 """
63 63 Defines an optional parameter::
64 64
65 65 param = param.getval() if isinstance(param, Optional) else param
66 66 param = param() if isinstance(param, Optional) else param
67 67
68 68 is equivalent of::
69 69
70 70 param = Optional.extract(param)
71 71
72 72 """
73 73
74 74 def __init__(self, type_):
75 75 self.type_ = type_
76 76
77 77 def __repr__(self):
78 78 return '<Optional:%s>' % self.type_.__repr__()
79 79
80 80 def __call__(self):
81 81 return self.getval()
82 82
83 83 def getval(self, evaluate_locals=None):
84 84 """
85 85 returns value from this Optional instance
86 86 """
87 87 if isinstance(self.type_, OAttr):
88 88 param_name = self.type_.attr_name
89 89 if evaluate_locals:
90 90 return evaluate_locals[param_name]
91 91 # use params name
92 92 return param_name
93 93 return self.type_
94 94
95 95 @classmethod
96 96 def extract(cls, val, evaluate_locals=None, binary=None):
97 97 """
98 98 Extracts value from Optional() instance
99 99
100 100 :param val:
101 101 :return: original value if it's not Optional instance else
102 102 value of instance
103 103 """
104 104 if isinstance(val, cls):
105 105 val = val.getval(evaluate_locals)
106 106
107 107 if binary:
108 108 val = str2bool(val)
109 109
110 110 return val
111 111
112 112
113 113 def parse_args(cli_args, key_prefix=''):
114 114 from rhodecode.lib.utils2 import (escape_split)
115 115 kwargs = collections.defaultdict(dict)
116 116 for el in escape_split(cli_args, ','):
117 117 kv = escape_split(el, '=', 1)
118 118 if len(kv) == 2:
119 119 k, v = kv
120 120 kwargs[key_prefix + k] = v
121 121 return kwargs
122 122
123 123
124 124 def get_origin(obj):
125 125 """
126 126 Get origin of permission from object.
127 127
128 128 :param obj:
129 129 """
130 130 origin = 'permission'
131 131
132 132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
133 133 # admin and owner case, maybe we should use dual string ?
134 134 origin = 'owner'
135 135 elif getattr(obj, 'owner_row', ''):
136 136 origin = 'owner'
137 137 elif getattr(obj, 'admin_row', ''):
138 138 origin = 'super-admin'
139 139 return origin
140 140
141 141
142 142 def store_update(updates, attr, name):
143 143 """
144 144 Stores param in updates dict if it's not instance of Optional
145 145 allows easy updates of passed in params
146 146 """
147 147 if not isinstance(attr, Optional):
148 148 updates[name] = attr
149 149
150 150
151 151 def has_superadmin_permission(apiuser):
152 152 """
153 153 Return True if apiuser is admin or return False
154 154
155 155 :param apiuser:
156 156 """
157 157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
158 158 return True
159 159 return False
160 160
161 161
162 162 def validate_repo_permissions(apiuser, repoid, repo, perms):
163 163 """
164 164 Raise JsonRPCError if apiuser is not authorized or return True
165 165
166 166 :param apiuser:
167 167 :param repoid:
168 168 :param repo:
169 169 :param perms:
170 170 """
171 171 if not HasRepoPermissionAnyApi(*perms)(
172 172 user=apiuser, repo_name=repo.repo_name):
173 173 raise JSONRPCError('repository `%s` does not exist' % repoid)
174 174
175 175 return True
176 176
177 177
178 178 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
179 179 """
180 180 Raise JsonRPCError if apiuser is not authorized or return True
181 181
182 182 :param apiuser:
183 183 :param repogroupid: just the id of repository group
184 184 :param repo_group: instance of repo_group
185 185 :param perms:
186 186 """
187 187 if not HasRepoGroupPermissionAnyApi(*perms)(
188 188 user=apiuser, group_name=repo_group.group_name):
189 189 raise JSONRPCError(
190 190 'repository group `%s` does not exist' % repogroupid)
191 191
192 192 return True
193 193
194 194
195 195 def validate_set_owner_permissions(apiuser, owner):
196 196 if isinstance(owner, Optional):
197 197 owner = get_user_or_error(apiuser.user_id)
198 198 else:
199 199 if has_superadmin_permission(apiuser):
200 200 owner = get_user_or_error(owner)
201 201 else:
202 202 # forbid setting owner for non-admins
203 203 raise JSONRPCError(
204 204 'Only RhodeCode super-admin can specify `owner` param')
205 205 return owner
206 206
207 207
208 208 def get_user_or_error(userid):
209 209 """
210 210 Get user by id or name or return JsonRPCError if not found
211 211
212 212 :param userid:
213 213 """
214 214 from rhodecode.model.user import UserModel
215 215 user_model = UserModel()
216 216
217 217 if isinstance(userid, int):
218 218 try:
219 219 user = user_model.get_user(userid)
220 220 except ValueError:
221 221 user = None
222 222 else:
223 223 user = user_model.get_by_username(userid)
224 224
225 225 if user is None:
226 226 raise JSONRPCError(
227 227 'user `%s` does not exist' % (userid,))
228 228 return user
229 229
230 230
231 231 def get_repo_or_error(repoid):
232 232 """
233 233 Get repo by id or name or return JsonRPCError if not found
234 234
235 235 :param repoid:
236 236 """
237 237 from rhodecode.model.repo import RepoModel
238 238 repo_model = RepoModel()
239 239
240 240 if isinstance(repoid, int):
241 241 try:
242 242 repo = repo_model.get_repo(repoid)
243 243 except ValueError:
244 244 repo = None
245 245 else:
246 246 repo = repo_model.get_by_repo_name(repoid)
247 247
248 248 if repo is None:
249 249 raise JSONRPCError(
250 250 'repository `%s` does not exist' % (repoid,))
251 251 return repo
252 252
253 253
254 254 def get_repo_group_or_error(repogroupid):
255 255 """
256 256 Get repo group by id or name or return JsonRPCError if not found
257 257
258 258 :param repogroupid:
259 259 """
260 260 from rhodecode.model.repo_group import RepoGroupModel
261 261 repo_group_model = RepoGroupModel()
262 262
263 263 if isinstance(repogroupid, int):
264 264 try:
265 265 repo_group = repo_group_model._get_repo_group(repogroupid)
266 266 except ValueError:
267 267 repo_group = None
268 268 else:
269 269 repo_group = repo_group_model.get_by_group_name(repogroupid)
270 270
271 271 if repo_group is None:
272 272 raise JSONRPCError(
273 273 'repository group `%s` does not exist' % (repogroupid,))
274 274 return repo_group
275 275
276 276
277 277 def get_user_group_or_error(usergroupid):
278 278 """
279 279 Get user group by id or name or return JsonRPCError if not found
280 280
281 281 :param usergroupid:
282 282 """
283 283 from rhodecode.model.user_group import UserGroupModel
284 284 user_group_model = UserGroupModel()
285 285
286 286 if isinstance(usergroupid, int):
287 287 try:
288 288 user_group = user_group_model.get_group(usergroupid)
289 289 except ValueError:
290 290 user_group = None
291 291 else:
292 292 user_group = user_group_model.get_by_name(usergroupid)
293 293
294 294 if user_group is None:
295 295 raise JSONRPCError(
296 296 'user group `%s` does not exist' % (usergroupid,))
297 297 return user_group
298 298
299 299
300 300 def get_perm_or_error(permid, prefix=None):
301 301 """
302 302 Get permission by id or name or return JsonRPCError if not found
303 303
304 304 :param permid:
305 305 """
306 306 from rhodecode.model.permission import PermissionModel
307 307
308 308 perm = PermissionModel.cls.get_by_key(permid)
309 309 if perm is None:
310 310 msg = 'permission `{}` does not exist.'.format(permid)
311 311 if prefix:
312 312 msg += ' Permission should start with prefix: `{}`'.format(prefix)
313 313 raise JSONRPCError(msg)
314 314
315 315 if prefix:
316 316 if not perm.permission_name.startswith(prefix):
317 317 raise JSONRPCError('permission `%s` is invalid, '
318 318 'should start with %s' % (permid, prefix))
319 319 return perm
320 320
321 321
322 322 def get_gist_or_error(gistid):
323 323 """
324 324 Get gist by id or gist_access_id or return JsonRPCError if not found
325 325
326 326 :param gistid:
327 327 """
328 328 from rhodecode.model.gist import GistModel
329 329
330 330 gist = GistModel.cls.get_by_access_id(gistid)
331 331 if gist is None:
332 332 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
333 333 return gist
334 334
335 335
336 336 def get_pull_request_or_error(pullrequestid):
337 337 """
338 338 Get pull request by id or return JsonRPCError if not found
339 339
340 340 :param pullrequestid:
341 341 """
342 342 from rhodecode.model.pull_request import PullRequestModel
343 343
344 344 try:
345 345 pull_request = PullRequestModel().get(int(pullrequestid))
346 346 except ValueError:
347 347 raise JSONRPCError('pullrequestid must be an integer')
348 348 if not pull_request:
349 349 raise JSONRPCError('pull request `%s` does not exist' % (
350 350 pullrequestid,))
351 351 return pull_request
352 352
353 353
354 354 def build_commit_data(rhodecode_vcs_repo, commit, detail_level):
355 355 commit2 = commit
356 356 commit1 = commit.first_parent
357 357
358 358 parsed_diff = []
359 359 if detail_level == 'extended':
360 360 for f_path in commit.added_paths:
361 361 parsed_diff.append(_get_commit_dict(filename=f_path, op='A'))
362 362 for f_path in commit.changed_paths:
363 363 parsed_diff.append(_get_commit_dict(filename=f_path, op='M'))
364 364 for f_path in commit.removed_paths:
365 365 parsed_diff.append(_get_commit_dict(filename=f_path, op='D'))
366 366
367 367 elif detail_level == 'full':
368 368 from rhodecode.lib import diffs
369 369
370 370 _diff = rhodecode_vcs_repo.get_diff(commit1, commit2,)
371 371 diff_processor = diffs.DiffProcessor(_diff, diff_format='newdiff', show_full_diff=True)
372 372
373 373 for dp in diff_processor.prepare():
374 374 del dp['stats']['ops']
375 375 _stats = dp['stats']
376 376 parsed_diff.append(_get_commit_dict(
377 377 filename=dp['filename'], op=dp['operation'],
378 378 new_revision=dp['new_revision'],
379 379 old_revision=dp['old_revision'],
380 380 raw_diff=dp['raw_diff'], stats=_stats))
381 381
382 382 return parsed_diff
383 383
384 384
385 385 def get_commit_or_error(ref, repo):
386 386 try:
387 387 ref_type, _, ref_hash = ref.split(':')
388 388 except ValueError:
389 389 raise JSONRPCError(
390 390 'Ref `{ref}` given in a wrong format. Please check the API'
391 391 ' documentation for more details'.format(ref=ref))
392 392 try:
393 393 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
394 394 # once get_commit supports ref_types
395 395 return get_commit_from_ref_name(repo, ref_hash)
396 396 except RepositoryError:
397 397 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
398 398
399 399
400 400 def _get_ref_hash(repo, type_, name):
401 401 vcs_repo = repo.scm_instance()
402 402 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
403 403 return vcs_repo.branches[name]
404 404 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
405 405 return vcs_repo.bookmarks[name]
406 406 else:
407 407 raise ValueError()
408 408
409 409
410 410 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
411 411 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
412 412
413 413 def _parse_ref(type_, name, hash_=None):
414 414 return type_, name, hash_
415 415
416 416 try:
417 417 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
418 418 except TypeError:
419 419 raise JSONRPCError(
420 420 'Ref `{ref}` given in a wrong format. Please check the API'
421 421 ' documentation for more details'.format(ref=ref))
422 422
423 423 if ref_type not in allowed_ref_types:
424 424 raise JSONRPCError(
425 425 'Ref `{ref}` type is not allowed. '
426 426 'Only:{allowed_refs} are possible.'.format(
427 427 ref=ref, allowed_refs=allowed_ref_types))
428 428
429 429 try:
430 430 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
431 431 except (KeyError, ValueError):
432 432 raise JSONRPCError(
433 433 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
434 434 type=ref_type, name=ref_name))
435 435
436 436 return ':'.join([ref_type, ref_name, ref_hash])
437 437
438 438
439 439 def _get_commit_dict(
440 440 filename, op, new_revision=None, old_revision=None,
441 441 raw_diff=None, stats=None):
442 442 if stats is None:
443 443 stats = {
444 444 "added": None,
445 445 "binary": None,
446 446 "deleted": None
447 447 }
448 448 return {
449 449 "filename": safe_str(filename),
450 450 "op": op,
451 451
452 452 # extra details
453 453 "new_revision": new_revision,
454 454 "old_revision": old_revision,
455 455
456 456 "raw_diff": raw_diff,
457 457 "stats": stats
458 458 }
@@ -1,19 +1,19 b''
1 1
2 2
3 # Copyright (C) 2015-2020 RhodeCode GmbH
3 # Copyright (C) 2015-2023 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,102 +1,102 b''
1 1
2 2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 """
23 23 NOTE:
24 24 Place for deprecated APIs here, if a call needs to be deprecated, please
25 25 put it here, and point to a new version
26 26 """
27 27 import logging
28 28
29 29 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method
30 30 from rhodecode.api.utils import Optional, OAttr
31 31
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 # permission check inside
37 37 @jsonrpc_method()
38 38 @jsonrpc_deprecated_method(
39 39 use_method='comment_commit', deprecated_at_version='3.4.0')
40 40 def changeset_comment(request, apiuser, repoid, revision, message,
41 41 userid=Optional(OAttr('apiuser')),
42 42 status=Optional(None)):
43 43 """
44 44 Set a changeset comment, and optionally change the status of the
45 45 changeset.
46 46
47 47 This command can only be run using an |authtoken| with admin
48 48 permissions on the |repo|.
49 49
50 50 :param apiuser: This is filled automatically from the |authtoken|.
51 51 :type apiuser: AuthUser
52 52 :param repoid: Set the repository name or repository ID.
53 53 :type repoid: str or int
54 54 :param revision: Specify the revision for which to set a comment.
55 55 :type revision: str
56 56 :param message: The comment text.
57 57 :type message: str
58 58 :param userid: Set the user name of the comment creator.
59 59 :type userid: Optional(str or int)
60 60 :param status: Set the comment status. The following are valid options:
61 61 * not_reviewed
62 62 * approved
63 63 * rejected
64 64 * under_review
65 65 :type status: str
66 66
67 67 Example error output:
68 68
69 69 .. code-block:: javascript
70 70
71 71 {
72 72 "id" : <id_given_in_input>,
73 73 "result" : {
74 74 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
75 75 "status_change": null or <status>,
76 76 "success": true
77 77 },
78 78 "error" : null
79 79 }
80 80
81 81 """
82 82 from .repo_api import comment_commit
83 83
84 84 return comment_commit(request=request,
85 85 apiuser=apiuser, repoid=repoid, commit_id=revision,
86 86 message=message, userid=userid, status=status)
87 87
88 88
89 89 @jsonrpc_method()
90 90 @jsonrpc_deprecated_method(
91 91 use_method='get_ip', deprecated_at_version='4.0.0')
92 92 def show_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
93 93 from .server_api import get_ip
94 94 return get_ip(request=request, apiuser=apiuser, userid=userid)
95 95
96 96
97 97 @jsonrpc_method()
98 98 @jsonrpc_deprecated_method(
99 99 use_method='get_user_locks', deprecated_at_version='4.0.0')
100 100 def get_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
101 101 from .user_api import get_user_locks
102 102 return get_user_locks(request=request, apiuser=apiuser, userid=userid) No newline at end of file
@@ -1,257 +1,257 b''
1 1
2 2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import time
24 24
25 25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 26 from rhodecode.api.exc import JSONRPCValidationError
27 27 from rhodecode.api.utils import (
28 28 Optional, OAttr, get_gist_or_error, get_user_or_error,
29 29 has_superadmin_permission)
30 30 from rhodecode.model.db import Session, or_
31 31 from rhodecode.model.gist import Gist, GistModel
32 32
33 33 log = logging.getLogger(__name__)
34 34
35 35
36 36 @jsonrpc_method()
37 37 def get_gist(request, apiuser, gistid, content=Optional(False)):
38 38 """
39 39 Get the specified gist, based on the gist ID.
40 40
41 41 :param apiuser: This is filled automatically from the |authtoken|.
42 42 :type apiuser: AuthUser
43 43 :param gistid: Set the id of the private or public gist
44 44 :type gistid: str
45 45 :param content: Return the gist content. Default is false.
46 46 :type content: Optional(bool)
47 47 """
48 48
49 49 gist = get_gist_or_error(gistid)
50 50 content = Optional.extract(content)
51 51
52 52 if not has_superadmin_permission(apiuser):
53 53 if gist.gist_owner != apiuser.user_id:
54 54 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
55 55 data = gist.get_api_data()
56 56
57 57 if content:
58 58 from rhodecode.model.gist import GistModel
59 59 rev, gist_files = GistModel().get_gist_files(gistid)
60 60 data['content'] = dict([(x.path, x.str_content) for x in gist_files])
61 61 return data
62 62
63 63
64 64 @jsonrpc_method()
65 65 def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))):
66 66 """
67 67 Get all gists for given user. If userid is empty returned gists
68 68 are for user who called the api
69 69
70 70 :param apiuser: This is filled automatically from the |authtoken|.
71 71 :type apiuser: AuthUser
72 72 :param userid: user to get gists for
73 73 :type userid: Optional(str or int)
74 74 """
75 75
76 76 if not has_superadmin_permission(apiuser):
77 77 # make sure normal user does not pass someone else userid,
78 78 # he is not allowed to do that
79 79 if not isinstance(userid, Optional) and userid != apiuser.user_id:
80 80 raise JSONRPCError(
81 81 'userid is not the same as your user'
82 82 )
83 83
84 84 if isinstance(userid, Optional):
85 85 user_id = apiuser.user_id
86 86 else:
87 87 user_id = get_user_or_error(userid).user_id
88 88
89 89 gists = []
90 90 _gists = Gist().query() \
91 91 .filter(or_(
92 92 Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \
93 93 .filter(Gist.gist_owner == user_id) \
94 94 .order_by(Gist.created_on.desc())
95 95 for gist in _gists:
96 96 gists.append(gist.get_api_data())
97 97 return gists
98 98
99 99
100 100 @jsonrpc_method()
101 101 def create_gist(
102 102 request, apiuser, files, gistid=Optional(None),
103 103 owner=Optional(OAttr('apiuser')),
104 104 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
105 105 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
106 106 description=Optional('')):
107 107 """
108 108 Creates a new Gist.
109 109
110 110 :param apiuser: This is filled automatically from the |authtoken|.
111 111 :type apiuser: AuthUser
112 112 :param files: files to be added to the gist. The data structure has
113 113 to match the following example::
114 114
115 115 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
116 116
117 117 :type files: dict
118 118 :param gistid: Set a custom id for the gist
119 119 :type gistid: Optional(str)
120 120 :param owner: Set the gist owner, defaults to api method caller
121 121 :type owner: Optional(str or int)
122 122 :param gist_type: type of gist ``public`` or ``private``
123 123 :type gist_type: Optional(str)
124 124 :param lifetime: time in minutes of gist lifetime
125 125 :type lifetime: Optional(int)
126 126 :param acl_level: acl level for this gist, can be
127 127 ``acl_public`` or ``acl_private`` If the value is set to
128 128 ``acl_private`` only logged in users are able to access this gist.
129 129 If not set it defaults to ``acl_public``.
130 130 :type acl_level: Optional(str)
131 131 :param description: gist description
132 132 :type description: Optional(str)
133 133
134 134 Example output:
135 135
136 136 .. code-block:: bash
137 137
138 138 id : <id_given_in_input>
139 139 result : {
140 140 "msg": "created new gist",
141 141 "gist": {}
142 142 }
143 143 error : null
144 144
145 145 Example error output:
146 146
147 147 .. code-block:: bash
148 148
149 149 id : <id_given_in_input>
150 150 result : null
151 151 error : {
152 152 "failed to create gist"
153 153 }
154 154
155 155 """
156 156 from rhodecode.model import validation_schema
157 157 from rhodecode.model.validation_schema.schemas import gist_schema
158 158
159 159 if isinstance(owner, Optional):
160 160 owner = apiuser.user_id
161 161
162 162 owner = get_user_or_error(owner)
163 163
164 164 lifetime = Optional.extract(lifetime)
165 165 schema = gist_schema.GistSchema().bind(
166 166 # bind the given values if it's allowed, however the deferred
167 167 # validator will still validate it according to other rules
168 168 lifetime_options=[lifetime])
169 169
170 170 try:
171 171 nodes = gist_schema.nodes_to_sequence(
172 172 files, colander_node=schema.get('nodes'))
173 173
174 174 schema_data = schema.deserialize(dict(
175 175 gistid=Optional.extract(gistid),
176 176 description=Optional.extract(description),
177 177 gist_type=Optional.extract(gist_type),
178 178 lifetime=lifetime,
179 179 gist_acl_level=Optional.extract(acl_level),
180 180 nodes=nodes
181 181 ))
182 182
183 183 # convert to safer format with just KEYs so we sure no duplicates
184 184 schema_data['nodes'] = gist_schema.sequence_to_nodes(
185 185 schema_data['nodes'], colander_node=schema.get('nodes'))
186 186
187 187 except validation_schema.Invalid as err:
188 188 raise JSONRPCValidationError(colander_exc=err)
189 189
190 190 try:
191 191 gist = GistModel().create(
192 192 owner=owner,
193 193 gist_id=schema_data['gistid'],
194 194 description=schema_data['description'],
195 195 gist_mapping=schema_data['nodes'],
196 196 gist_type=schema_data['gist_type'],
197 197 lifetime=schema_data['lifetime'],
198 198 gist_acl_level=schema_data['gist_acl_level'])
199 199 Session().commit()
200 200 return {
201 201 'msg': 'created new gist',
202 202 'gist': gist.get_api_data()
203 203 }
204 204 except Exception:
205 205 log.exception('Error occurred during creation of gist')
206 206 raise JSONRPCError('failed to create gist')
207 207
208 208
209 209 @jsonrpc_method()
210 210 def delete_gist(request, apiuser, gistid):
211 211 """
212 212 Deletes existing gist
213 213
214 214 :param apiuser: filled automatically from apikey
215 215 :type apiuser: AuthUser
216 216 :param gistid: id of gist to delete
217 217 :type gistid: str
218 218
219 219 Example output:
220 220
221 221 .. code-block:: bash
222 222
223 223 id : <id_given_in_input>
224 224 result : {
225 225 "deleted gist ID: <gist_id>",
226 226 "gist": null
227 227 }
228 228 error : null
229 229
230 230 Example error output:
231 231
232 232 .. code-block:: bash
233 233
234 234 id : <id_given_in_input>
235 235 result : null
236 236 error : {
237 237 "failed to delete gist ID:<gist_id>"
238 238 }
239 239
240 240 """
241 241
242 242 gist = get_gist_or_error(gistid)
243 243 if not has_superadmin_permission(apiuser):
244 244 if gist.gist_owner != apiuser.user_id:
245 245 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
246 246
247 247 try:
248 248 GistModel().delete(gist)
249 249 Session().commit()
250 250 return {
251 251 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,),
252 252 'gist': None
253 253 }
254 254 except Exception:
255 255 log.exception('Error occured during gist deletion')
256 256 raise JSONRPCError('failed to delete gist ID:%s'
257 257 % (gist.gist_access_id,)) No newline at end of file
@@ -1,1113 +1,1113 b''
1 1
2 2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23
24 24 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
25 25 from rhodecode.api.utils import (
26 26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 28 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
29 29 from rhodecode.lib import channelstream
30 30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
31 31 from rhodecode.lib.base import vcs_operation_context
32 32 from rhodecode.lib.utils2 import str2bool
33 33 from rhodecode.lib.vcs.backends.base import unicode_to_reference
34 34 from rhodecode.model.changeset_status import ChangesetStatusModel
35 35 from rhodecode.model.comment import CommentsModel
36 36 from rhodecode.model.db import (
37 37 Session, ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers)
38 38 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
39 39 from rhodecode.model.settings import SettingsModel
40 40 from rhodecode.model.validation_schema import Invalid
41 41 from rhodecode.model.validation_schema.schemas.reviewer_schema import ReviewerListSchema
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 @jsonrpc_method()
47 47 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None),
48 48 merge_state=Optional(False)):
49 49 """
50 50 Get a pull request based on the given ID.
51 51
52 52 :param apiuser: This is filled automatically from the |authtoken|.
53 53 :type apiuser: AuthUser
54 54 :param repoid: Optional, repository name or repository ID from where
55 55 the pull request was opened.
56 56 :type repoid: str or int
57 57 :param pullrequestid: ID of the requested pull request.
58 58 :type pullrequestid: int
59 59 :param merge_state: Optional calculate merge state for each repository.
60 60 This could result in longer time to fetch the data
61 61 :type merge_state: bool
62 62
63 63 Example output:
64 64
65 65 .. code-block:: bash
66 66
67 67 "id": <id_given_in_input>,
68 68 "result":
69 69 {
70 70 "pull_request_id": "<pull_request_id>",
71 71 "url": "<url>",
72 72 "title": "<title>",
73 73 "description": "<description>",
74 74 "status" : "<status>",
75 75 "created_on": "<date_time_created>",
76 76 "updated_on": "<date_time_updated>",
77 77 "versions": "<number_or_versions_of_pr>",
78 78 "commit_ids": [
79 79 ...
80 80 "<commit_id>",
81 81 "<commit_id>",
82 82 ...
83 83 ],
84 84 "review_status": "<review_status>",
85 85 "mergeable": {
86 86 "status": "<bool>",
87 87 "message": "<message>",
88 88 },
89 89 "source": {
90 90 "clone_url": "<clone_url>",
91 91 "repository": "<repository_name>",
92 92 "reference":
93 93 {
94 94 "name": "<name>",
95 95 "type": "<type>",
96 96 "commit_id": "<commit_id>",
97 97 }
98 98 },
99 99 "target": {
100 100 "clone_url": "<clone_url>",
101 101 "repository": "<repository_name>",
102 102 "reference":
103 103 {
104 104 "name": "<name>",
105 105 "type": "<type>",
106 106 "commit_id": "<commit_id>",
107 107 }
108 108 },
109 109 "merge": {
110 110 "clone_url": "<clone_url>",
111 111 "reference":
112 112 {
113 113 "name": "<name>",
114 114 "type": "<type>",
115 115 "commit_id": "<commit_id>",
116 116 }
117 117 },
118 118 "author": <user_obj>,
119 119 "reviewers": [
120 120 ...
121 121 {
122 122 "user": "<user_obj>",
123 123 "review_status": "<review_status>",
124 124 }
125 125 ...
126 126 ]
127 127 },
128 128 "error": null
129 129 """
130 130
131 131 pull_request = get_pull_request_or_error(pullrequestid)
132 132 if Optional.extract(repoid):
133 133 repo = get_repo_or_error(repoid)
134 134 else:
135 135 repo = pull_request.target_repo
136 136
137 137 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
138 138 raise JSONRPCError('repository `%s` or pull request `%s` '
139 139 'does not exist' % (repoid, pullrequestid))
140 140
141 141 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
142 142 # otherwise we can lock the repo on calculation of merge state while update/merge
143 143 # is happening.
144 144 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
145 145 merge_state = Optional.extract(merge_state, binary=True) and pr_created
146 146 data = pull_request.get_api_data(with_merge_state=merge_state)
147 147 return data
148 148
149 149
150 150 @jsonrpc_method()
151 151 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
152 152 merge_state=Optional(False)):
153 153 """
154 154 Get all pull requests from the repository specified in `repoid`.
155 155
156 156 :param apiuser: This is filled automatically from the |authtoken|.
157 157 :type apiuser: AuthUser
158 158 :param repoid: Optional repository name or repository ID.
159 159 :type repoid: str or int
160 160 :param status: Only return pull requests with the specified status.
161 161 Valid options are.
162 162 * ``new`` (default)
163 163 * ``open``
164 164 * ``closed``
165 165 :type status: str
166 166 :param merge_state: Optional calculate merge state for each repository.
167 167 This could result in longer time to fetch the data
168 168 :type merge_state: bool
169 169
170 170 Example output:
171 171
172 172 .. code-block:: bash
173 173
174 174 "id": <id_given_in_input>,
175 175 "result":
176 176 [
177 177 ...
178 178 {
179 179 "pull_request_id": "<pull_request_id>",
180 180 "url": "<url>",
181 181 "title" : "<title>",
182 182 "description": "<description>",
183 183 "status": "<status>",
184 184 "created_on": "<date_time_created>",
185 185 "updated_on": "<date_time_updated>",
186 186 "commit_ids": [
187 187 ...
188 188 "<commit_id>",
189 189 "<commit_id>",
190 190 ...
191 191 ],
192 192 "review_status": "<review_status>",
193 193 "mergeable": {
194 194 "status": "<bool>",
195 195 "message: "<message>",
196 196 },
197 197 "source": {
198 198 "clone_url": "<clone_url>",
199 199 "reference":
200 200 {
201 201 "name": "<name>",
202 202 "type": "<type>",
203 203 "commit_id": "<commit_id>",
204 204 }
205 205 },
206 206 "target": {
207 207 "clone_url": "<clone_url>",
208 208 "reference":
209 209 {
210 210 "name": "<name>",
211 211 "type": "<type>",
212 212 "commit_id": "<commit_id>",
213 213 }
214 214 },
215 215 "merge": {
216 216 "clone_url": "<clone_url>",
217 217 "reference":
218 218 {
219 219 "name": "<name>",
220 220 "type": "<type>",
221 221 "commit_id": "<commit_id>",
222 222 }
223 223 },
224 224 "author": <user_obj>,
225 225 "reviewers": [
226 226 ...
227 227 {
228 228 "user": "<user_obj>",
229 229 "review_status": "<review_status>",
230 230 }
231 231 ...
232 232 ]
233 233 }
234 234 ...
235 235 ],
236 236 "error": null
237 237
238 238 """
239 239 repo = get_repo_or_error(repoid)
240 240 if not has_superadmin_permission(apiuser):
241 241 _perms = (
242 242 'repository.admin', 'repository.write', 'repository.read',)
243 243 validate_repo_permissions(apiuser, repoid, repo, _perms)
244 244
245 245 status = Optional.extract(status)
246 246 merge_state = Optional.extract(merge_state, binary=True)
247 247 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
248 248 order_by='id', order_dir='desc')
249 249 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
250 250 return data
251 251
252 252
253 253 @jsonrpc_method()
254 254 def merge_pull_request(
255 255 request, apiuser, pullrequestid, repoid=Optional(None),
256 256 userid=Optional(OAttr('apiuser'))):
257 257 """
258 258 Merge the pull request specified by `pullrequestid` into its target
259 259 repository.
260 260
261 261 :param apiuser: This is filled automatically from the |authtoken|.
262 262 :type apiuser: AuthUser
263 263 :param repoid: Optional, repository name or repository ID of the
264 264 target repository to which the |pr| is to be merged.
265 265 :type repoid: str or int
266 266 :param pullrequestid: ID of the pull request which shall be merged.
267 267 :type pullrequestid: int
268 268 :param userid: Merge the pull request as this user.
269 269 :type userid: Optional(str or int)
270 270
271 271 Example output:
272 272
273 273 .. code-block:: bash
274 274
275 275 "id": <id_given_in_input>,
276 276 "result": {
277 277 "executed": "<bool>",
278 278 "failure_reason": "<int>",
279 279 "merge_status_message": "<str>",
280 280 "merge_commit_id": "<merge_commit_id>",
281 281 "possible": "<bool>",
282 282 "merge_ref": {
283 283 "commit_id": "<commit_id>",
284 284 "type": "<type>",
285 285 "name": "<name>"
286 286 }
287 287 },
288 288 "error": null
289 289 """
290 290 pull_request = get_pull_request_or_error(pullrequestid)
291 291 if Optional.extract(repoid):
292 292 repo = get_repo_or_error(repoid)
293 293 else:
294 294 repo = pull_request.target_repo
295 295 auth_user = apiuser
296 296
297 297 if not isinstance(userid, Optional):
298 298 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
299 299 user=apiuser, repo_name=repo.repo_name)
300 300 if has_superadmin_permission(apiuser) or is_repo_admin:
301 301 apiuser = get_user_or_error(userid)
302 302 auth_user = apiuser.AuthUser()
303 303 else:
304 304 raise JSONRPCError('userid is not the same as your user')
305 305
306 306 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
307 307 raise JSONRPCError(
308 308 'Operation forbidden because pull request is in state {}, '
309 309 'only state {} is allowed.'.format(
310 310 pull_request.pull_request_state, PullRequest.STATE_CREATED))
311 311
312 312 with pull_request.set_state(PullRequest.STATE_UPDATING):
313 313 check = MergeCheck.validate(pull_request, auth_user=auth_user,
314 314 translator=request.translate)
315 315 merge_possible = not check.failed
316 316
317 317 if not merge_possible:
318 318 error_messages = []
319 319 for err_type, error_msg in check.errors:
320 320 error_msg = request.translate(error_msg)
321 321 error_messages.append(error_msg)
322 322
323 323 reasons = ','.join(error_messages)
324 324 raise JSONRPCError(
325 325 'merge not possible for following reasons: {}'.format(reasons))
326 326
327 327 target_repo = pull_request.target_repo
328 328 extras = vcs_operation_context(
329 329 request.environ, repo_name=target_repo.repo_name,
330 330 username=auth_user.username, action='push',
331 331 scm=target_repo.repo_type)
332 332 with pull_request.set_state(PullRequest.STATE_UPDATING):
333 333 merge_response = PullRequestModel().merge_repo(
334 334 pull_request, apiuser, extras=extras)
335 335 if merge_response.executed:
336 336 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
337 337
338 338 Session().commit()
339 339
340 340 # In previous versions the merge response directly contained the merge
341 341 # commit id. It is now contained in the merge reference object. To be
342 342 # backwards compatible we have to extract it again.
343 343 merge_response = merge_response.asdict()
344 344 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
345 345
346 346 return merge_response
347 347
348 348
349 349 @jsonrpc_method()
350 350 def get_pull_request_comments(
351 351 request, apiuser, pullrequestid, repoid=Optional(None)):
352 352 """
353 353 Get all comments of pull request specified with the `pullrequestid`
354 354
355 355 :param apiuser: This is filled automatically from the |authtoken|.
356 356 :type apiuser: AuthUser
357 357 :param repoid: Optional repository name or repository ID.
358 358 :type repoid: str or int
359 359 :param pullrequestid: The pull request ID.
360 360 :type pullrequestid: int
361 361
362 362 Example output:
363 363
364 364 .. code-block:: bash
365 365
366 366 id : <id_given_in_input>
367 367 result : [
368 368 {
369 369 "comment_author": {
370 370 "active": true,
371 371 "full_name_or_username": "Tom Gore",
372 372 "username": "admin"
373 373 },
374 374 "comment_created_on": "2017-01-02T18:43:45.533",
375 375 "comment_f_path": null,
376 376 "comment_id": 25,
377 377 "comment_lineno": null,
378 378 "comment_status": {
379 379 "status": "under_review",
380 380 "status_lbl": "Under Review"
381 381 },
382 382 "comment_text": "Example text",
383 383 "comment_type": null,
384 384 "comment_last_version: 0,
385 385 "pull_request_version": null,
386 386 "comment_commit_id": None,
387 387 "comment_pull_request_id": <pull_request_id>
388 388 }
389 389 ],
390 390 error : null
391 391 """
392 392
393 393 pull_request = get_pull_request_or_error(pullrequestid)
394 394 if Optional.extract(repoid):
395 395 repo = get_repo_or_error(repoid)
396 396 else:
397 397 repo = pull_request.target_repo
398 398
399 399 if not PullRequestModel().check_user_read(
400 400 pull_request, apiuser, api=True):
401 401 raise JSONRPCError('repository `%s` or pull request `%s` '
402 402 'does not exist' % (repoid, pullrequestid))
403 403
404 404 (pull_request_latest,
405 405 pull_request_at_ver,
406 406 pull_request_display_obj,
407 407 at_version) = PullRequestModel().get_pr_version(
408 408 pull_request.pull_request_id, version=None)
409 409
410 410 versions = pull_request_display_obj.versions()
411 411 ver_map = {
412 412 ver.pull_request_version_id: cnt
413 413 for cnt, ver in enumerate(versions, 1)
414 414 }
415 415
416 416 # GENERAL COMMENTS with versions #
417 417 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
418 418 q = q.order_by(ChangesetComment.comment_id.asc())
419 419 general_comments = q.all()
420 420
421 421 # INLINE COMMENTS with versions #
422 422 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
423 423 q = q.order_by(ChangesetComment.comment_id.asc())
424 424 inline_comments = q.all()
425 425
426 426 data = []
427 427 for comment in inline_comments + general_comments:
428 428 full_data = comment.get_api_data()
429 429 pr_version_id = None
430 430 if comment.pull_request_version_id:
431 431 pr_version_id = 'v{}'.format(
432 432 ver_map[comment.pull_request_version_id])
433 433
434 434 # sanitize some entries
435 435
436 436 full_data['pull_request_version'] = pr_version_id
437 437 full_data['comment_author'] = {
438 438 'username': full_data['comment_author'].username,
439 439 'full_name_or_username': full_data['comment_author'].full_name_or_username,
440 440 'active': full_data['comment_author'].active,
441 441 }
442 442
443 443 if full_data['comment_status']:
444 444 full_data['comment_status'] = {
445 445 'status': full_data['comment_status'][0].status,
446 446 'status_lbl': full_data['comment_status'][0].status_lbl,
447 447 }
448 448 else:
449 449 full_data['comment_status'] = {}
450 450
451 451 data.append(full_data)
452 452 return data
453 453
454 454
455 455 @jsonrpc_method()
456 456 def comment_pull_request(
457 457 request, apiuser, pullrequestid, repoid=Optional(None),
458 458 message=Optional(None), commit_id=Optional(None), status=Optional(None),
459 459 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
460 460 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
461 461 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
462 462 """
463 463 Comment on the pull request specified with the `pullrequestid`,
464 464 in the |repo| specified by the `repoid`, and optionally change the
465 465 review status.
466 466
467 467 :param apiuser: This is filled automatically from the |authtoken|.
468 468 :type apiuser: AuthUser
469 469 :param repoid: Optional repository name or repository ID.
470 470 :type repoid: str or int
471 471 :param pullrequestid: The pull request ID.
472 472 :type pullrequestid: int
473 473 :param commit_id: Specify the commit_id for which to set a comment. If
474 474 given commit_id is different than latest in the PR status
475 475 change won't be performed.
476 476 :type commit_id: str
477 477 :param message: The text content of the comment.
478 478 :type message: str
479 479 :param status: (**Optional**) Set the approval status of the pull
480 480 request. One of: 'not_reviewed', 'approved', 'rejected',
481 481 'under_review'
482 482 :type status: str
483 483 :param comment_type: Comment type, one of: 'note', 'todo'
484 484 :type comment_type: Optional(str), default: 'note'
485 485 :param resolves_comment_id: id of comment which this one will resolve
486 486 :type resolves_comment_id: Optional(int)
487 487 :param extra_recipients: list of user ids or usernames to add
488 488 notifications for this comment. Acts like a CC for notification
489 489 :type extra_recipients: Optional(list)
490 490 :param userid: Comment on the pull request as this user
491 491 :type userid: Optional(str or int)
492 492 :param send_email: Define if this comment should also send email notification
493 493 :type send_email: Optional(bool)
494 494
495 495 Example output:
496 496
497 497 .. code-block:: bash
498 498
499 499 id : <id_given_in_input>
500 500 result : {
501 501 "pull_request_id": "<Integer>",
502 502 "comment_id": "<Integer>",
503 503 "status": {"given": <given_status>,
504 504 "was_changed": <bool status_was_actually_changed> },
505 505 },
506 506 error : null
507 507 """
508 508 _ = request.translate
509 509
510 510 pull_request = get_pull_request_or_error(pullrequestid)
511 511 if Optional.extract(repoid):
512 512 repo = get_repo_or_error(repoid)
513 513 else:
514 514 repo = pull_request.target_repo
515 515
516 516 db_repo_name = repo.repo_name
517 517 auth_user = apiuser
518 518 if not isinstance(userid, Optional):
519 519 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
520 520 user=apiuser, repo_name=db_repo_name)
521 521 if has_superadmin_permission(apiuser) or is_repo_admin:
522 522 apiuser = get_user_or_error(userid)
523 523 auth_user = apiuser.AuthUser()
524 524 else:
525 525 raise JSONRPCError('userid is not the same as your user')
526 526
527 527 if pull_request.is_closed():
528 528 raise JSONRPCError(f'pull request `{pullrequestid}` comment failed, pull request is closed')
529 529
530 530 if not PullRequestModel().check_user_read(
531 531 pull_request, apiuser, api=True):
532 532 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
533 533 message = Optional.extract(message)
534 534 status = Optional.extract(status)
535 535 commit_id = Optional.extract(commit_id)
536 536 comment_type = Optional.extract(comment_type)
537 537 resolves_comment_id = Optional.extract(resolves_comment_id)
538 538 extra_recipients = Optional.extract(extra_recipients)
539 539 send_email = Optional.extract(send_email, binary=True)
540 540
541 541 if not message and not status:
542 542 raise JSONRPCError(
543 543 'Both message and status parameters are missing. '
544 544 'At least one is required.')
545 545
546 546 if status and status not in (st[0] for st in ChangesetStatus.STATUSES):
547 547 raise JSONRPCError(f'Unknown comment status: `{status}`')
548 548
549 549 if commit_id and commit_id not in pull_request.revisions:
550 550 raise JSONRPCError(f'Invalid commit_id `{commit_id}` for this pull request.')
551 551
552 552 allowed_to_change_status = PullRequestModel().check_user_change_status(
553 553 pull_request, apiuser)
554 554
555 555 # if commit_id is passed re-validated if user is allowed to change status
556 556 # based on the latest commit_id from the PR
557 557 if commit_id:
558 558 commit_idx = pull_request.revisions.index(commit_id)
559 559 if commit_idx != 0:
560 560 log.warning('Resetting allowed_to_change_status = False because commit is NOT the latest in pull-request')
561 561 allowed_to_change_status = False
562 562
563 563 if resolves_comment_id:
564 564 comment = ChangesetComment.get(resolves_comment_id)
565 565 if not comment:
566 566 raise JSONRPCError(f'Invalid resolves_comment_id `{resolves_comment_id}` for this pull request.')
567 567 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
568 568 raise JSONRPCError(f'Comment `{resolves_comment_id}` is wrong type for setting status to resolved.')
569 569
570 570 text = message
571 571 status_label = ChangesetStatus.get_status_lbl(status)
572 572 if status and allowed_to_change_status:
573 573 st_message = ('Status change %(transition_icon)s %(status)s'
574 574 % {'transition_icon': '>', 'status': status_label})
575 575 text = message or st_message
576 576
577 577 rc_config = SettingsModel().get_all_settings()
578 578 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
579 579
580 580 status_change = status and allowed_to_change_status
581 581 comment = CommentsModel().create(
582 582 text=text,
583 583 repo=pull_request.target_repo.repo_id,
584 584 user=apiuser.user_id,
585 585 pull_request=pull_request.pull_request_id,
586 586 f_path=None,
587 587 line_no=None,
588 588 status_change=(status_label if status_change else None),
589 589 status_change_type=(status if status_change else None),
590 590 closing_pr=False,
591 591 renderer=renderer,
592 592 comment_type=comment_type,
593 593 resolves_comment_id=resolves_comment_id,
594 594 auth_user=auth_user,
595 595 extra_recipients=extra_recipients,
596 596 send_email=send_email
597 597 )
598 598
599 599 is_inline = comment.is_inline
600 600
601 601 if allowed_to_change_status and status:
602 602 old_calculated_status = pull_request.calculated_review_status()
603 603 ChangesetStatusModel().set_status(
604 604 pull_request.target_repo.repo_id,
605 605 status,
606 606 apiuser.user_id,
607 607 comment,
608 608 pull_request=pull_request.pull_request_id
609 609 )
610 610 Session().flush()
611 611
612 612 Session().commit()
613 613
614 614 PullRequestModel().trigger_pull_request_hook(
615 615 pull_request, apiuser, 'comment',
616 616 data={'comment': comment})
617 617
618 618 if allowed_to_change_status and status:
619 619 # we now calculate the status of pull request, and based on that
620 620 # calculation we set the commits status
621 621 calculated_status = pull_request.calculated_review_status()
622 622 if old_calculated_status != calculated_status:
623 623 PullRequestModel().trigger_pull_request_hook(
624 624 pull_request, apiuser, 'review_status_change',
625 625 data={'status': calculated_status})
626 626
627 627 data = {
628 628 'pull_request_id': pull_request.pull_request_id,
629 629 'comment_id': comment.comment_id if comment else None,
630 630 'status': {'given': status, 'was_changed': status_change},
631 631 }
632 632
633 633 comment_broadcast_channel = channelstream.comment_channel(
634 634 db_repo_name, pull_request_obj=pull_request)
635 635
636 636 comment_data = data
637 637 comment_type = 'inline' if is_inline else 'general'
638 638 channelstream.comment_channelstream_push(
639 639 request, comment_broadcast_channel, apiuser,
640 640 _('posted a new {} comment').format(comment_type),
641 641 comment_data=comment_data)
642 642
643 643 return data
644 644
645 645
646 646 def _reviewers_validation(obj_list):
647 647 schema = ReviewerListSchema()
648 648 try:
649 649 reviewer_objects = schema.deserialize(obj_list)
650 650 except Invalid as err:
651 651 raise JSONRPCValidationError(colander_exc=err)
652 652
653 653 # validate users
654 654 for reviewer_object in reviewer_objects:
655 655 user = get_user_or_error(reviewer_object['username'])
656 656 reviewer_object['user_id'] = user.user_id
657 657 return reviewer_objects
658 658
659 659
660 660 @jsonrpc_method()
661 661 def create_pull_request(
662 662 request, apiuser, source_repo, target_repo, source_ref, target_ref,
663 663 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
664 664 description_renderer=Optional(''),
665 665 reviewers=Optional(None), observers=Optional(None)):
666 666 """
667 667 Creates a new pull request.
668 668
669 669 Accepts refs in the following formats:
670 670
671 671 * branch:<branch_name>:<sha>
672 672 * branch:<branch_name>
673 673 * bookmark:<bookmark_name>:<sha> (Mercurial only)
674 674 * bookmark:<bookmark_name> (Mercurial only)
675 675
676 676 :param apiuser: This is filled automatically from the |authtoken|.
677 677 :type apiuser: AuthUser
678 678 :param source_repo: Set the source repository name.
679 679 :type source_repo: str
680 680 :param target_repo: Set the target repository name.
681 681 :type target_repo: str
682 682 :param source_ref: Set the source ref name.
683 683 :type source_ref: str
684 684 :param target_ref: Set the target ref name.
685 685 :type target_ref: str
686 686 :param owner: user_id or username
687 687 :type owner: Optional(str)
688 688 :param title: Optionally Set the pull request title, it's generated otherwise
689 689 :type title: str
690 690 :param description: Set the pull request description.
691 691 :type description: Optional(str)
692 692 :type description_renderer: Optional(str)
693 693 :param description_renderer: Set pull request renderer for the description.
694 694 It should be 'rst', 'markdown' or 'plain'. If not give default
695 695 system renderer will be used
696 696 :param reviewers: Set the new pull request reviewers list.
697 697 Reviewer defined by review rules will be added automatically to the
698 698 defined list.
699 699 :type reviewers: Optional(list)
700 700 Accepts username strings or objects of the format:
701 701
702 702 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
703 703 :param observers: Set the new pull request observers list.
704 704 Reviewer defined by review rules will be added automatically to the
705 705 defined list. This feature is only available in RhodeCode EE
706 706 :type observers: Optional(list)
707 707 Accepts username strings or objects of the format:
708 708
709 709 [{'username': 'nick', 'reasons': ['original author']}]
710 710 """
711 711
712 712 source_db_repo = get_repo_or_error(source_repo)
713 713 target_db_repo = get_repo_or_error(target_repo)
714 714 if not has_superadmin_permission(apiuser):
715 715 _perms = ('repository.admin', 'repository.write', 'repository.read',)
716 716 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
717 717
718 718 owner = validate_set_owner_permissions(apiuser, owner)
719 719
720 720 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
721 721 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
722 722
723 723 get_commit_or_error(full_source_ref, source_db_repo)
724 724 get_commit_or_error(full_target_ref, target_db_repo)
725 725
726 726 reviewer_objects = Optional.extract(reviewers) or []
727 727 observer_objects = Optional.extract(observers) or []
728 728
729 729 # serialize and validate passed in given reviewers
730 730 if reviewer_objects:
731 731 reviewer_objects = _reviewers_validation(reviewer_objects)
732 732
733 733 if observer_objects:
734 734 observer_objects = _reviewers_validation(reviewer_objects)
735 735
736 736 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
737 737 PullRequestModel().get_reviewer_functions()
738 738
739 739 source_ref_obj = unicode_to_reference(full_source_ref)
740 740 target_ref_obj = unicode_to_reference(full_target_ref)
741 741
742 742 # recalculate reviewers logic, to make sure we can validate this
743 743 default_reviewers_data = get_default_reviewers_data(
744 744 owner,
745 745 source_db_repo,
746 746 source_ref_obj,
747 747 target_db_repo,
748 748 target_ref_obj,
749 749 )
750 750
751 751 # now MERGE our given with the calculated from the default rules
752 752 just_reviewers = [
753 753 x for x in default_reviewers_data['reviewers']
754 754 if x['role'] == PullRequestReviewers.ROLE_REVIEWER]
755 755 reviewer_objects = just_reviewers + reviewer_objects
756 756
757 757 try:
758 758 reviewers = validate_default_reviewers(
759 759 reviewer_objects, default_reviewers_data)
760 760 except ValueError as e:
761 761 raise JSONRPCError('Reviewers Validation: {}'.format(e))
762 762
763 763 # now MERGE our given with the calculated from the default rules
764 764 just_observers = [
765 765 x for x in default_reviewers_data['reviewers']
766 766 if x['role'] == PullRequestReviewers.ROLE_OBSERVER]
767 767 observer_objects = just_observers + observer_objects
768 768
769 769 try:
770 770 observers = validate_observers(
771 771 observer_objects, default_reviewers_data)
772 772 except ValueError as e:
773 773 raise JSONRPCError('Observer Validation: {}'.format(e))
774 774
775 775 title = Optional.extract(title)
776 776 if not title:
777 777 title_source_ref = source_ref_obj.name
778 778 title = PullRequestModel().generate_pullrequest_title(
779 779 source=source_repo,
780 780 source_ref=title_source_ref,
781 781 target=target_repo
782 782 )
783 783
784 784 diff_info = default_reviewers_data['diff_info']
785 785 common_ancestor_id = diff_info['ancestor']
786 786 # NOTE(marcink): reversed is consistent with how we open it in the WEB interface
787 787 commits = [commit['commit_id'] for commit in reversed(diff_info['commits'])]
788 788
789 789 if not common_ancestor_id:
790 790 raise JSONRPCError('no common ancestor found between specified references')
791 791
792 792 if not commits:
793 793 raise JSONRPCError('no commits found for merge between specified references')
794 794
795 795 # recalculate target ref based on ancestor
796 796 full_target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, common_ancestor_id))
797 797
798 798 # fetch renderer, if set fallback to plain in case of PR
799 799 rc_config = SettingsModel().get_all_settings()
800 800 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
801 801 description = Optional.extract(description)
802 802 description_renderer = Optional.extract(description_renderer) or default_system_renderer
803 803
804 804 pull_request = PullRequestModel().create(
805 805 created_by=owner.user_id,
806 806 source_repo=source_repo,
807 807 source_ref=full_source_ref,
808 808 target_repo=target_repo,
809 809 target_ref=full_target_ref,
810 810 common_ancestor_id=common_ancestor_id,
811 811 revisions=commits,
812 812 reviewers=reviewers,
813 813 observers=observers,
814 814 title=title,
815 815 description=description,
816 816 description_renderer=description_renderer,
817 817 reviewer_data=default_reviewers_data,
818 818 auth_user=apiuser
819 819 )
820 820
821 821 Session().commit()
822 822 data = {
823 823 'msg': 'Created new pull request `{}`'.format(title),
824 824 'pull_request_id': pull_request.pull_request_id,
825 825 }
826 826 return data
827 827
828 828
829 829 @jsonrpc_method()
830 830 def update_pull_request(
831 831 request, apiuser, pullrequestid, repoid=Optional(None),
832 832 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
833 833 reviewers=Optional(None), observers=Optional(None), update_commits=Optional(None)):
834 834 """
835 835 Updates a pull request.
836 836
837 837 :param apiuser: This is filled automatically from the |authtoken|.
838 838 :type apiuser: AuthUser
839 839 :param repoid: Optional repository name or repository ID.
840 840 :type repoid: str or int
841 841 :param pullrequestid: The pull request ID.
842 842 :type pullrequestid: int
843 843 :param title: Set the pull request title.
844 844 :type title: str
845 845 :param description: Update pull request description.
846 846 :type description: Optional(str)
847 847 :type description_renderer: Optional(str)
848 848 :param description_renderer: Update pull request renderer for the description.
849 849 It should be 'rst', 'markdown' or 'plain'
850 850 :param reviewers: Update pull request reviewers list with new value.
851 851 :type reviewers: Optional(list)
852 852 Accepts username strings or objects of the format:
853 853
854 854 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
855 855 :param observers: Update pull request observers list with new value.
856 856 :type observers: Optional(list)
857 857 Accepts username strings or objects of the format:
858 858
859 859 [{'username': 'nick', 'reasons': ['should be aware about this PR']}]
860 860 :param update_commits: Trigger update of commits for this pull request
861 861 :type: update_commits: Optional(bool)
862 862
863 863 Example output:
864 864
865 865 .. code-block:: bash
866 866
867 867 id : <id_given_in_input>
868 868 result : {
869 869 "msg": "Updated pull request `63`",
870 870 "pull_request": <pull_request_object>,
871 871 "updated_reviewers": {
872 872 "added": [
873 873 "username"
874 874 ],
875 875 "removed": []
876 876 },
877 877 "updated_observers": {
878 878 "added": [
879 879 "username"
880 880 ],
881 881 "removed": []
882 882 },
883 883 "updated_commits": {
884 884 "added": [
885 885 "<sha1_hash>"
886 886 ],
887 887 "common": [
888 888 "<sha1_hash>",
889 889 "<sha1_hash>",
890 890 ],
891 891 "removed": []
892 892 }
893 893 }
894 894 error : null
895 895 """
896 896
897 897 pull_request = get_pull_request_or_error(pullrequestid)
898 898 if Optional.extract(repoid):
899 899 repo = get_repo_or_error(repoid)
900 900 else:
901 901 repo = pull_request.target_repo
902 902
903 903 if not PullRequestModel().check_user_update(
904 904 pull_request, apiuser, api=True):
905 905 raise JSONRPCError(
906 906 'pull request `%s` update failed, no permission to update.' % (
907 907 pullrequestid,))
908 908 if pull_request.is_closed():
909 909 raise JSONRPCError(
910 910 'pull request `%s` update failed, pull request is closed' % (
911 911 pullrequestid,))
912 912
913 913 reviewer_objects = Optional.extract(reviewers) or []
914 914 observer_objects = Optional.extract(observers) or []
915 915
916 916 title = Optional.extract(title)
917 917 description = Optional.extract(description)
918 918 description_renderer = Optional.extract(description_renderer)
919 919
920 920 # Update title/description
921 921 title_changed = False
922 922 if title or description:
923 923 PullRequestModel().edit(
924 924 pull_request,
925 925 title or pull_request.title,
926 926 description or pull_request.description,
927 927 description_renderer or pull_request.description_renderer,
928 928 apiuser)
929 929 Session().commit()
930 930 title_changed = True
931 931
932 932 commit_changes = {"added": [], "common": [], "removed": []}
933 933
934 934 # Update commits
935 935 commits_changed = False
936 936 if str2bool(Optional.extract(update_commits)):
937 937
938 938 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
939 939 raise JSONRPCError(
940 940 'Operation forbidden because pull request is in state {}, '
941 941 'only state {} is allowed.'.format(
942 942 pull_request.pull_request_state, PullRequest.STATE_CREATED))
943 943
944 944 with pull_request.set_state(PullRequest.STATE_UPDATING):
945 945 if PullRequestModel().has_valid_update_type(pull_request):
946 946 db_user = apiuser.get_instance()
947 947 update_response = PullRequestModel().update_commits(
948 948 pull_request, db_user)
949 949 commit_changes = update_response.changes or commit_changes
950 950 Session().commit()
951 951 commits_changed = True
952 952
953 953 # Update reviewers
954 954 # serialize and validate passed in given reviewers
955 955 if reviewer_objects:
956 956 reviewer_objects = _reviewers_validation(reviewer_objects)
957 957
958 958 if observer_objects:
959 959 observer_objects = _reviewers_validation(reviewer_objects)
960 960
961 961 # re-use stored rules
962 962 default_reviewers_data = pull_request.reviewer_data
963 963
964 964 __, validate_default_reviewers, validate_observers = \
965 965 PullRequestModel().get_reviewer_functions()
966 966
967 967 if reviewer_objects:
968 968 try:
969 969 reviewers = validate_default_reviewers(reviewer_objects, default_reviewers_data)
970 970 except ValueError as e:
971 971 raise JSONRPCError('Reviewers Validation: {}'.format(e))
972 972 else:
973 973 reviewers = []
974 974
975 975 if observer_objects:
976 976 try:
977 977 observers = validate_default_reviewers(reviewer_objects, default_reviewers_data)
978 978 except ValueError as e:
979 979 raise JSONRPCError('Observer Validation: {}'.format(e))
980 980 else:
981 981 observers = []
982 982
983 983 reviewers_changed = False
984 984 reviewers_changes = {"added": [], "removed": []}
985 985 if reviewers:
986 986 old_calculated_status = pull_request.calculated_review_status()
987 987 added_reviewers, removed_reviewers = \
988 988 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser.get_instance())
989 989
990 990 reviewers_changes['added'] = sorted(
991 991 [get_user_or_error(n).username for n in added_reviewers])
992 992 reviewers_changes['removed'] = sorted(
993 993 [get_user_or_error(n).username for n in removed_reviewers])
994 994 Session().commit()
995 995
996 996 # trigger status changed if change in reviewers changes the status
997 997 calculated_status = pull_request.calculated_review_status()
998 998 if old_calculated_status != calculated_status:
999 999 PullRequestModel().trigger_pull_request_hook(
1000 1000 pull_request, apiuser, 'review_status_change',
1001 1001 data={'status': calculated_status})
1002 1002 reviewers_changed = True
1003 1003
1004 1004 observers_changed = False
1005 1005 observers_changes = {"added": [], "removed": []}
1006 1006 if observers:
1007 1007 added_observers, removed_observers = \
1008 1008 PullRequestModel().update_observers(pull_request, observers, apiuser.get_instance())
1009 1009
1010 1010 observers_changes['added'] = sorted(
1011 1011 [get_user_or_error(n).username for n in added_observers])
1012 1012 observers_changes['removed'] = sorted(
1013 1013 [get_user_or_error(n).username for n in removed_observers])
1014 1014 Session().commit()
1015 1015
1016 1016 reviewers_changed = True
1017 1017
1018 1018 # push changed to channelstream
1019 1019 if commits_changed or reviewers_changed or observers_changed:
1020 1020 pr_broadcast_channel = channelstream.pr_channel(pull_request)
1021 1021 msg = 'Pull request was updated.'
1022 1022 channelstream.pr_update_channelstream_push(
1023 1023 request, pr_broadcast_channel, apiuser, msg)
1024 1024
1025 1025 data = {
1026 1026 'msg': 'Updated pull request `{}`'.format(pull_request.pull_request_id),
1027 1027 'pull_request': pull_request.get_api_data(),
1028 1028 'updated_commits': commit_changes,
1029 1029 'updated_reviewers': reviewers_changes,
1030 1030 'updated_observers': observers_changes,
1031 1031 }
1032 1032
1033 1033 return data
1034 1034
1035 1035
1036 1036 @jsonrpc_method()
1037 1037 def close_pull_request(
1038 1038 request, apiuser, pullrequestid, repoid=Optional(None),
1039 1039 userid=Optional(OAttr('apiuser')), message=Optional('')):
1040 1040 """
1041 1041 Close the pull request specified by `pullrequestid`.
1042 1042
1043 1043 :param apiuser: This is filled automatically from the |authtoken|.
1044 1044 :type apiuser: AuthUser
1045 1045 :param repoid: Repository name or repository ID to which the pull
1046 1046 request belongs.
1047 1047 :type repoid: str or int
1048 1048 :param pullrequestid: ID of the pull request to be closed.
1049 1049 :type pullrequestid: int
1050 1050 :param userid: Close the pull request as this user.
1051 1051 :type userid: Optional(str or int)
1052 1052 :param message: Optional message to close the Pull Request with. If not
1053 1053 specified it will be generated automatically.
1054 1054 :type message: Optional(str)
1055 1055
1056 1056 Example output:
1057 1057
1058 1058 .. code-block:: bash
1059 1059
1060 1060 "id": <id_given_in_input>,
1061 1061 "result": {
1062 1062 "pull_request_id": "<int>",
1063 1063 "close_status": "<str:status_lbl>,
1064 1064 "closed": "<bool>"
1065 1065 },
1066 1066 "error": null
1067 1067
1068 1068 """
1069 1069 _ = request.translate
1070 1070
1071 1071 pull_request = get_pull_request_or_error(pullrequestid)
1072 1072 if Optional.extract(repoid):
1073 1073 repo = get_repo_or_error(repoid)
1074 1074 else:
1075 1075 repo = pull_request.target_repo
1076 1076
1077 1077 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
1078 1078 user=apiuser, repo_name=repo.repo_name)
1079 1079 if not isinstance(userid, Optional):
1080 1080 if has_superadmin_permission(apiuser) or is_repo_admin:
1081 1081 apiuser = get_user_or_error(userid)
1082 1082 else:
1083 1083 raise JSONRPCError('userid is not the same as your user')
1084 1084
1085 1085 if pull_request.is_closed():
1086 1086 raise JSONRPCError(
1087 1087 'pull request `%s` is already closed' % (pullrequestid,))
1088 1088
1089 1089 # only owner or admin or person with write permissions
1090 1090 allowed_to_close = PullRequestModel().check_user_update(
1091 1091 pull_request, apiuser, api=True)
1092 1092
1093 1093 if not allowed_to_close:
1094 1094 raise JSONRPCError(
1095 1095 'pull request `%s` close failed, no permission to close.' % (
1096 1096 pullrequestid,))
1097 1097
1098 1098 # message we're using to close the PR, else it's automatically generated
1099 1099 message = Optional.extract(message)
1100 1100
1101 1101 # finally close the PR, with proper message comment
1102 1102 comment, status = PullRequestModel().close_pull_request_with_comment(
1103 1103 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1104 1104 status_lbl = ChangesetStatus.get_status_lbl(status)
1105 1105
1106 1106 Session().commit()
1107 1107
1108 1108 data = {
1109 1109 'pull_request_id': pull_request.pull_request_id,
1110 1110 'close_status': status_lbl,
1111 1111 'closed': True,
1112 1112 }
1113 1113 return data
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 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