##// END OF EJS Templates
docs: updated copyrights to 2019
marcink -
r3363:f08e98b1 default
parent child Browse files
Show More

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

@@ -1,63 +1,63 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 RhodeCode, a web based repository management software
24 24 versioning implementation: http://www.python.org/dev/peps/pep-0386/
25 25 """
26 26
27 27 import os
28 28 import sys
29 29 import platform
30 30
31 31 VERSION = tuple(open(os.path.join(
32 32 os.path.dirname(__file__), 'VERSION')).read().split('.'))
33 33
34 34 BACKENDS = {
35 35 'hg': 'Mercurial repository',
36 36 'git': 'Git repository',
37 37 'svn': 'Subversion repository',
38 38 }
39 39
40 40 CELERY_ENABLED = False
41 41 CELERY_EAGER = False
42 42
43 43 # link to config for pyramid
44 44 CONFIG = {}
45 45
46 46 # Populated with the settings dictionary from application init in
47 47 # rhodecode.conf.environment.load_pyramid_environment
48 48 PYRAMID_SETTINGS = {}
49 49
50 50 # Linked module for extensions
51 51 EXTENSIONS = {}
52 52
53 53 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
54 54 __dbversion__ = 91 # defines current db version for migrations
55 55 __platform__ = platform.system()
56 56 __license__ = 'AGPLv3, and Commercial License'
57 57 __author__ = 'RhodeCode GmbH'
58 58 __url__ = 'https://code.rhodecode.com'
59 59
60 60 is_windows = __platform__ in ['Windows']
61 61 is_unix = not is_windows
62 62 is_test = False
63 63 disable_error_handler = False
@@ -1,548 +1,548 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 inspect
22 22 import itertools
23 23 import logging
24 24 import sys
25 25 import types
26 26 import fnmatch
27 27
28 28 import decorator
29 29 import venusian
30 30 from collections import OrderedDict
31 31
32 32 from pyramid.exceptions import ConfigurationError
33 33 from pyramid.renderers import render
34 34 from pyramid.response import Response
35 35 from pyramid.httpexceptions import HTTPNotFound
36 36
37 37 from rhodecode.api.exc import (
38 38 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
39 39 from rhodecode.apps._base import TemplateArgs
40 40 from rhodecode.lib.auth import AuthUser
41 41 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
42 42 from rhodecode.lib.exc_tracking import store_exception
43 43 from rhodecode.lib.ext_json import json
44 44 from rhodecode.lib.utils2 import safe_str
45 45 from rhodecode.lib.plugins.utils import get_plugin_settings
46 46 from rhodecode.model.db import User, UserApiKeys
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 DEFAULT_RENDERER = 'jsonrpc_renderer'
51 51 DEFAULT_URL = '/_admin/apiv2'
52 52
53 53
54 54 def find_methods(jsonrpc_methods, pattern):
55 55 matches = OrderedDict()
56 56 if not isinstance(pattern, (list, tuple)):
57 57 pattern = [pattern]
58 58
59 59 for single_pattern in pattern:
60 60 for method_name, method in jsonrpc_methods.items():
61 61 if fnmatch.fnmatch(method_name, single_pattern):
62 62 matches[method_name] = method
63 63 return matches
64 64
65 65
66 66 class ExtJsonRenderer(object):
67 67 """
68 68 Custom renderer that mkaes use of our ext_json lib
69 69
70 70 """
71 71
72 72 def __init__(self, serializer=json.dumps, **kw):
73 73 """ Any keyword arguments will be passed to the ``serializer``
74 74 function."""
75 75 self.serializer = serializer
76 76 self.kw = kw
77 77
78 78 def __call__(self, info):
79 79 """ Returns a plain JSON-encoded string with content-type
80 80 ``application/json``. The content-type may be overridden by
81 81 setting ``request.response.content_type``."""
82 82
83 83 def _render(value, system):
84 84 request = system.get('request')
85 85 if request is not None:
86 86 response = request.response
87 87 ct = response.content_type
88 88 if ct == response.default_content_type:
89 89 response.content_type = 'application/json'
90 90
91 91 return self.serializer(value, **self.kw)
92 92
93 93 return _render
94 94
95 95
96 96 def jsonrpc_response(request, result):
97 97 rpc_id = getattr(request, 'rpc_id', None)
98 98 response = request.response
99 99
100 100 # store content_type before render is called
101 101 ct = response.content_type
102 102
103 103 ret_value = ''
104 104 if rpc_id:
105 105 ret_value = {
106 106 'id': rpc_id,
107 107 'result': result,
108 108 'error': None,
109 109 }
110 110
111 111 # fetch deprecation warnings, and store it inside results
112 112 deprecation = getattr(request, 'rpc_deprecation', None)
113 113 if deprecation:
114 114 ret_value['DEPRECATION_WARNING'] = deprecation
115 115
116 116 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
117 117 response.body = safe_str(raw_body, response.charset)
118 118
119 119 if ct == response.default_content_type:
120 120 response.content_type = 'application/json'
121 121
122 122 return response
123 123
124 124
125 125 def jsonrpc_error(request, message, retid=None, code=None):
126 126 """
127 127 Generate a Response object with a JSON-RPC error body
128 128
129 129 :param code:
130 130 :param retid:
131 131 :param message:
132 132 """
133 133 err_dict = {'id': retid, 'result': None, 'error': message}
134 134 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
135 135 return Response(
136 136 body=body,
137 137 status=code,
138 138 content_type='application/json'
139 139 )
140 140
141 141
142 142 def exception_view(exc, request):
143 143 rpc_id = getattr(request, 'rpc_id', None)
144 144
145 145 if isinstance(exc, JSONRPCError):
146 146 fault_message = safe_str(exc.message)
147 147 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
148 148 elif isinstance(exc, JSONRPCValidationError):
149 149 colander_exc = exc.colander_exception
150 150 # TODO(marcink): think maybe of nicer way to serialize errors ?
151 151 fault_message = colander_exc.asdict()
152 152 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
153 153 elif isinstance(exc, JSONRPCForbidden):
154 154 fault_message = 'Access was denied to this resource.'
155 155 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
156 156 elif isinstance(exc, HTTPNotFound):
157 157 method = request.rpc_method
158 158 log.debug('json-rpc method `%s` not found in list of '
159 159 'api calls: %s, rpc_id:%s',
160 160 method, request.registry.jsonrpc_methods.keys(), rpc_id)
161 161
162 162 similar = 'none'
163 163 try:
164 164 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
165 165 similar_found = find_methods(
166 166 request.registry.jsonrpc_methods, similar_paterns)
167 167 similar = ', '.join(similar_found.keys()) or similar
168 168 except Exception:
169 169 # make the whole above block safe
170 170 pass
171 171
172 172 fault_message = "No such method: {}. Similar methods: {}".format(
173 173 method, similar)
174 174 else:
175 175 fault_message = 'undefined error'
176 176 exc_info = exc.exc_info()
177 177 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
178 178
179 179 return jsonrpc_error(request, fault_message, rpc_id)
180 180
181 181
182 182 def request_view(request):
183 183 """
184 184 Main request handling method. It handles all logic to call a specific
185 185 exposed method
186 186 """
187 187
188 188 # check if we can find this session using api_key, get_by_auth_token
189 189 # search not expired tokens only
190 190
191 191 try:
192 192 api_user = User.get_by_auth_token(request.rpc_api_key)
193 193
194 194 if api_user is None:
195 195 return jsonrpc_error(
196 196 request, retid=request.rpc_id, message='Invalid API KEY')
197 197
198 198 if not api_user.active:
199 199 return jsonrpc_error(
200 200 request, retid=request.rpc_id,
201 201 message='Request from this user not allowed')
202 202
203 203 # check if we are allowed to use this IP
204 204 auth_u = AuthUser(
205 205 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
206 206 if not auth_u.ip_allowed:
207 207 return jsonrpc_error(
208 208 request, retid=request.rpc_id,
209 209 message='Request from IP:%s not allowed' % (
210 210 request.rpc_ip_addr,))
211 211 else:
212 212 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
213 213
214 214 # register our auth-user
215 215 request.rpc_user = auth_u
216 216 request.environ['rc_auth_user_id'] = auth_u.user_id
217 217
218 218 # now check if token is valid for API
219 219 auth_token = request.rpc_api_key
220 220 token_match = api_user.authenticate_by_token(
221 221 auth_token, roles=[UserApiKeys.ROLE_API])
222 222 invalid_token = not token_match
223 223
224 224 log.debug('Checking if API KEY is valid with proper role')
225 225 if invalid_token:
226 226 return jsonrpc_error(
227 227 request, retid=request.rpc_id,
228 228 message='API KEY invalid or, has bad role for an API call')
229 229
230 230 except Exception:
231 231 log.exception('Error on API AUTH')
232 232 return jsonrpc_error(
233 233 request, retid=request.rpc_id, message='Invalid API KEY')
234 234
235 235 method = request.rpc_method
236 236 func = request.registry.jsonrpc_methods[method]
237 237
238 238 # now that we have a method, add request._req_params to
239 239 # self.kargs and dispatch control to WGIController
240 240 argspec = inspect.getargspec(func)
241 241 arglist = argspec[0]
242 242 defaults = map(type, argspec[3] or [])
243 243 default_empty = types.NotImplementedType
244 244
245 245 # kw arguments required by this method
246 246 func_kwargs = dict(itertools.izip_longest(
247 247 reversed(arglist), reversed(defaults), fillvalue=default_empty))
248 248
249 249 # This attribute will need to be first param of a method that uses
250 250 # api_key, which is translated to instance of user at that name
251 251 user_var = 'apiuser'
252 252 request_var = 'request'
253 253
254 254 for arg in [user_var, request_var]:
255 255 if arg not in arglist:
256 256 return jsonrpc_error(
257 257 request,
258 258 retid=request.rpc_id,
259 259 message='This method [%s] does not support '
260 260 'required parameter `%s`' % (func.__name__, arg))
261 261
262 262 # get our arglist and check if we provided them as args
263 263 for arg, default in func_kwargs.items():
264 264 if arg in [user_var, request_var]:
265 265 # user_var and request_var are pre-hardcoded parameters and we
266 266 # don't need to do any translation
267 267 continue
268 268
269 269 # skip the required param check if it's default value is
270 270 # NotImplementedType (default_empty)
271 271 if default == default_empty and arg not in request.rpc_params:
272 272 return jsonrpc_error(
273 273 request,
274 274 retid=request.rpc_id,
275 275 message=('Missing non optional `%s` arg in JSON DATA' % arg)
276 276 )
277 277
278 278 # sanitize extra passed arguments
279 279 for k in request.rpc_params.keys()[:]:
280 280 if k not in func_kwargs:
281 281 del request.rpc_params[k]
282 282
283 283 call_params = request.rpc_params
284 284 call_params.update({
285 285 'request': request,
286 286 'apiuser': auth_u
287 287 })
288 288
289 289 # register some common functions for usage
290 290 attach_context_attributes(
291 291 TemplateArgs(), request, request.rpc_user.user_id)
292 292
293 293 try:
294 294 ret_value = func(**call_params)
295 295 return jsonrpc_response(request, ret_value)
296 296 except JSONRPCBaseError:
297 297 raise
298 298 except Exception:
299 299 log.exception('Unhandled exception occurred on api call: %s', func)
300 300 exc_info = sys.exc_info()
301 301 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
302 302 return jsonrpc_error(
303 303 request, retid=request.rpc_id, message='Internal server error')
304 304
305 305
306 306 def setup_request(request):
307 307 """
308 308 Parse a JSON-RPC request body. It's used inside the predicates method
309 309 to validate and bootstrap requests for usage in rpc calls.
310 310
311 311 We need to raise JSONRPCError here if we want to return some errors back to
312 312 user.
313 313 """
314 314
315 315 log.debug('Executing setup request: %r', request)
316 316 request.rpc_ip_addr = get_ip_addr(request.environ)
317 317 # TODO(marcink): deprecate GET at some point
318 318 if request.method not in ['POST', 'GET']:
319 319 log.debug('unsupported request method "%s"', request.method)
320 320 raise JSONRPCError(
321 321 'unsupported request method "%s". Please use POST' % request.method)
322 322
323 323 if 'CONTENT_LENGTH' not in request.environ:
324 324 log.debug("No Content-Length")
325 325 raise JSONRPCError("Empty body, No Content-Length in request")
326 326
327 327 else:
328 328 length = request.environ['CONTENT_LENGTH']
329 329 log.debug('Content-Length: %s', length)
330 330
331 331 if length == 0:
332 332 log.debug("Content-Length is 0")
333 333 raise JSONRPCError("Content-Length is 0")
334 334
335 335 raw_body = request.body
336 336 try:
337 337 json_body = json.loads(raw_body)
338 338 except ValueError as e:
339 339 # catch JSON errors Here
340 340 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
341 341
342 342 request.rpc_id = json_body.get('id')
343 343 request.rpc_method = json_body.get('method')
344 344
345 345 # check required base parameters
346 346 try:
347 347 api_key = json_body.get('api_key')
348 348 if not api_key:
349 349 api_key = json_body.get('auth_token')
350 350
351 351 if not api_key:
352 352 raise KeyError('api_key or auth_token')
353 353
354 354 # TODO(marcink): support passing in token in request header
355 355
356 356 request.rpc_api_key = api_key
357 357 request.rpc_id = json_body['id']
358 358 request.rpc_method = json_body['method']
359 359 request.rpc_params = json_body['args'] \
360 360 if isinstance(json_body['args'], dict) else {}
361 361
362 362 log.debug('method: %s, params: %s', request.rpc_method, request.rpc_params)
363 363 except KeyError as e:
364 364 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
365 365
366 366 log.debug('setup complete, now handling method:%s rpcid:%s',
367 367 request.rpc_method, request.rpc_id, )
368 368
369 369
370 370 class RoutePredicate(object):
371 371 def __init__(self, val, config):
372 372 self.val = val
373 373
374 374 def text(self):
375 375 return 'jsonrpc route = %s' % self.val
376 376
377 377 phash = text
378 378
379 379 def __call__(self, info, request):
380 380 if self.val:
381 381 # potentially setup and bootstrap our call
382 382 setup_request(request)
383 383
384 384 # Always return True so that even if it isn't a valid RPC it
385 385 # will fall through to the underlaying handlers like notfound_view
386 386 return True
387 387
388 388
389 389 class NotFoundPredicate(object):
390 390 def __init__(self, val, config):
391 391 self.val = val
392 392 self.methods = config.registry.jsonrpc_methods
393 393
394 394 def text(self):
395 395 return 'jsonrpc method not found = {}.'.format(self.val)
396 396
397 397 phash = text
398 398
399 399 def __call__(self, info, request):
400 400 return hasattr(request, 'rpc_method')
401 401
402 402
403 403 class MethodPredicate(object):
404 404 def __init__(self, val, config):
405 405 self.method = val
406 406
407 407 def text(self):
408 408 return 'jsonrpc method = %s' % self.method
409 409
410 410 phash = text
411 411
412 412 def __call__(self, context, request):
413 413 # we need to explicitly return False here, so pyramid doesn't try to
414 414 # execute our view directly. We need our main handler to execute things
415 415 return getattr(request, 'rpc_method') == self.method
416 416
417 417
418 418 def add_jsonrpc_method(config, view, **kwargs):
419 419 # pop the method name
420 420 method = kwargs.pop('method', None)
421 421
422 422 if method is None:
423 423 raise ConfigurationError(
424 424 'Cannot register a JSON-RPC method without specifying the "method"')
425 425
426 426 # we define custom predicate, to enable to detect conflicting methods,
427 427 # those predicates are kind of "translation" from the decorator variables
428 428 # to internal predicates names
429 429
430 430 kwargs['jsonrpc_method'] = method
431 431
432 432 # register our view into global view store for validation
433 433 config.registry.jsonrpc_methods[method] = view
434 434
435 435 # we're using our main request_view handler, here, so each method
436 436 # has a unified handler for itself
437 437 config.add_view(request_view, route_name='apiv2', **kwargs)
438 438
439 439
440 440 class jsonrpc_method(object):
441 441 """
442 442 decorator that works similar to @add_view_config decorator,
443 443 but tailored for our JSON RPC
444 444 """
445 445
446 446 venusian = venusian # for testing injection
447 447
448 448 def __init__(self, method=None, **kwargs):
449 449 self.method = method
450 450 self.kwargs = kwargs
451 451
452 452 def __call__(self, wrapped):
453 453 kwargs = self.kwargs.copy()
454 454 kwargs['method'] = self.method or wrapped.__name__
455 455 depth = kwargs.pop('_depth', 0)
456 456
457 457 def callback(context, name, ob):
458 458 config = context.config.with_package(info.module)
459 459 config.add_jsonrpc_method(view=ob, **kwargs)
460 460
461 461 info = venusian.attach(wrapped, callback, category='pyramid',
462 462 depth=depth + 1)
463 463 if info.scope == 'class':
464 464 # ensure that attr is set if decorating a class method
465 465 kwargs.setdefault('attr', wrapped.__name__)
466 466
467 467 kwargs['_info'] = info.codeinfo # fbo action_method
468 468 return wrapped
469 469
470 470
471 471 class jsonrpc_deprecated_method(object):
472 472 """
473 473 Marks method as deprecated, adds log.warning, and inject special key to
474 474 the request variable to mark method as deprecated.
475 475 Also injects special docstring that extract_docs will catch to mark
476 476 method as deprecated.
477 477
478 478 :param use_method: specify which method should be used instead of
479 479 the decorated one
480 480
481 481 Use like::
482 482
483 483 @jsonrpc_method()
484 484 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
485 485 def old_func(request, apiuser, arg1, arg2):
486 486 ...
487 487 """
488 488
489 489 def __init__(self, use_method, deprecated_at_version):
490 490 self.use_method = use_method
491 491 self.deprecated_at_version = deprecated_at_version
492 492 self.deprecated_msg = ''
493 493
494 494 def __call__(self, func):
495 495 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
496 496 method=self.use_method)
497 497
498 498 docstring = """\n
499 499 .. deprecated:: {version}
500 500
501 501 {deprecation_message}
502 502
503 503 {original_docstring}
504 504 """
505 505 func.__doc__ = docstring.format(
506 506 version=self.deprecated_at_version,
507 507 deprecation_message=self.deprecated_msg,
508 508 original_docstring=func.__doc__)
509 509 return decorator.decorator(self.__wrapper, func)
510 510
511 511 def __wrapper(self, func, *fargs, **fkwargs):
512 512 log.warning('DEPRECATED API CALL on function %s, please '
513 513 'use `%s` instead', func, self.use_method)
514 514 # alter function docstring to mark as deprecated, this is picked up
515 515 # via fabric file that generates API DOC.
516 516 result = func(*fargs, **fkwargs)
517 517
518 518 request = fargs[0]
519 519 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
520 520 return result
521 521
522 522
523 523 def includeme(config):
524 524 plugin_module = 'rhodecode.api'
525 525 plugin_settings = get_plugin_settings(
526 526 plugin_module, config.registry.settings)
527 527
528 528 if not hasattr(config.registry, 'jsonrpc_methods'):
529 529 config.registry.jsonrpc_methods = OrderedDict()
530 530
531 531 # match filter by given method only
532 532 config.add_view_predicate('jsonrpc_method', MethodPredicate)
533 533 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
534 534
535 535 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
536 536 serializer=json.dumps, indent=4))
537 537 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
538 538
539 539 config.add_route_predicate(
540 540 'jsonrpc_call', RoutePredicate)
541 541
542 542 config.add_route(
543 543 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
544 544
545 545 config.scan(plugin_module, ignore='rhodecode.api.tests')
546 546 # register some exception handling view
547 547 config.add_view(exception_view, context=JSONRPCBaseError)
548 548 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
@@ -1,42 +1,42 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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,19 +1,19 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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,52 +1,52 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.meta import Session
24 24 from rhodecode.model.user import UserModel
25 25 from rhodecode.model.auth_token import AuthTokenModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 27
28 28
29 29 @pytest.fixture(scope="class")
30 30 def testuser_api(request, baseapp):
31 31 cls = request.cls
32 32
33 33 # ADMIN USER
34 34 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
35 35 cls.apikey = cls.usr.api_key
36 36
37 37 # REGULAR USER
38 38 cls.test_user = UserModel().create_or_update(
39 39 username='test-api',
40 40 password='test',
41 41 email='test@api.rhodecode.org',
42 42 firstname='first',
43 43 lastname='last'
44 44 )
45 45 # create TOKEN for user, if he doesn't have one
46 46 if not cls.test_user.api_key:
47 47 AuthTokenModel().create(
48 48 user=cls.test_user, description=u'TEST_USER_TOKEN')
49 49
50 50 Session().commit()
51 51 cls.TEST_USER_LOGIN = cls.test_user.username
52 52 cls.apikey_regular = cls.test_user.api_key
@@ -1,62 +1,62 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import Repository, RepositoryField
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, assert_error)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestAddFieldToRepo(object):
30 30 def test_api_add_field_to_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, 'add_field_to_repo',
35 35 repoid=repo_name,
36 36 key='extra_field',
37 37 label='extra_field_label',
38 38 description='extra_field_desc')
39 39 response = api_call(self.app, params)
40 40 expected = {
41 41 'msg': 'Added new repository field `extra_field`',
42 42 'success': True,
43 43 }
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 repo = Repository.get_by_repo_name(repo_name)
47 47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
48 48 _data = repo_field.get_dict()
49 49 assert _data['field_desc'] == 'extra_field_desc'
50 50 assert _data['field_key'] == 'extra_field'
51 51 assert _data['field_label'] == 'extra_field_label'
52 52
53 53 id_, params = build_data(
54 54 self.apikey, 'add_field_to_repo',
55 55 repoid=repo_name,
56 56 key='extra_field',
57 57 label='extra_field_label',
58 58 description='extra_field_desc')
59 59 response = api_call(self.app, params)
60 60 expected = 'Field with key `extra_field` exists for repo `%s`' % (
61 61 repo_name)
62 62 assert_error(id_, expected, given=response.body)
@@ -1,72 +1,72 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.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 TestAddUserToUserGroup(object):
31 31 def test_api_add_user_to_user_group(self, user_util):
32 32 group = user_util.create_user_group()
33 33 user = user_util.create_user()
34 34 group_name = group.users_group_name
35 35 user_name = user.username
36 36 id_, params = build_data(
37 37 self.apikey, 'add_user_to_user_group',
38 38 usergroupid=group_name, userid=user_name)
39 39 response = api_call(self.app, params)
40 40 expected = {
41 41 'msg': 'added member `%s` to user group `%s`' % (
42 42 user_name, group_name
43 43 ),
44 44 'success': True
45 45 }
46 46 assert_ok(id_, expected, given=response.body)
47 47
48 48 def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util):
49 49 user = user_util.create_user()
50 50 user_name = user.username
51 51 id_, params = build_data(
52 52 self.apikey, 'add_user_to_user_group',
53 53 usergroupid='false-group',
54 54 userid=user_name)
55 55 response = api_call(self.app, params)
56 56
57 57 expected = 'user group `%s` does not exist' % 'false-group'
58 58 assert_error(id_, expected, given=response.body)
59 59
60 60 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
61 61 def test_api_add_user_to_user_group_exception_occurred(self, user_util):
62 62 group = user_util.create_user_group()
63 63 user = user_util.create_user()
64 64 group_name = group.users_group_name
65 65 user_name = user.username
66 66 id_, params = build_data(
67 67 self.apikey, 'add_user_to_user_group',
68 68 usergroupid=group_name, userid=user_name)
69 69 response = api_call(self.app, params)
70 70
71 71 expected = 'failed to add member to user group `%s`' % (group_name,)
72 72 assert_error(id_, expected, given=response.body)
@@ -1,131 +1,131 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.api.utils import Optional, OAttr
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApi(object):
30 30 maxDiff = None
31 31
32 32 def test_Optional_object(self):
33 33
34 34 option1 = Optional(None)
35 35 assert '<Optional:%s>' % (None,) == repr(option1)
36 36 assert option1() is None
37 37
38 38 assert 1 == Optional.extract(Optional(1))
39 39 assert 'example' == Optional.extract('example')
40 40
41 41 def test_Optional_OAttr(self):
42 42 option1 = Optional(OAttr('apiuser'))
43 43 assert 'apiuser' == Optional.extract(option1)
44 44
45 45 def test_OAttr_object(self):
46 46 oattr1 = OAttr('apiuser')
47 47 assert '<OptionalAttr:apiuser>' == repr(oattr1)
48 48 assert oattr1() == oattr1
49 49
50 50 def test_api_wrong_key(self):
51 51 id_, params = build_data('trololo', 'get_user')
52 52 response = api_call(self.app, params)
53 53
54 54 expected = 'Invalid API KEY'
55 55 assert_error(id_, expected, given=response.body)
56 56
57 57 def test_api_missing_non_optional_param(self):
58 58 id_, params = build_data(self.apikey, 'get_repo')
59 59 response = api_call(self.app, params)
60 60
61 61 expected = 'Missing non optional `repoid` arg in JSON DATA'
62 62 assert_error(id_, expected, given=response.body)
63 63
64 64 def test_api_missing_non_optional_param_args_null(self):
65 65 id_, params = build_data(self.apikey, 'get_repo')
66 66 params = params.replace('"args": {}', '"args": null')
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'Missing non optional `repoid` arg in JSON DATA'
70 70 assert_error(id_, expected, given=response.body)
71 71
72 72 def test_api_missing_non_optional_param_args_bad(self):
73 73 id_, params = build_data(self.apikey, 'get_repo')
74 74 params = params.replace('"args": {}', '"args": 1')
75 75 response = api_call(self.app, params)
76 76
77 77 expected = 'Missing non optional `repoid` arg in JSON DATA'
78 78 assert_error(id_, expected, given=response.body)
79 79
80 80 def test_api_non_existing_method(self, request):
81 81 id_, params = build_data(self.apikey, 'not_existing', args='xx')
82 82 response = api_call(self.app, params)
83 83 expected = 'No such method: not_existing. Similar methods: none'
84 84 assert_error(id_, expected, given=response.body)
85 85
86 86 def test_api_non_existing_method_have_similar(self, request):
87 87 id_, params = build_data(self.apikey, 'comment', args='xx')
88 88 response = api_call(self.app, params)
89 89 expected = 'No such method: comment. Similar methods: changeset_comment, comment_pull_request, get_pull_request_comments, comment_commit'
90 90 assert_error(id_, expected, given=response.body)
91 91
92 92 def test_api_disabled_user(self, request):
93 93
94 94 def set_active(active):
95 95 from rhodecode.model.db import Session, User
96 96 user = User.get_by_auth_token(self.apikey)
97 97 user.active = active
98 98 Session().add(user)
99 99 Session().commit()
100 100
101 101 request.addfinalizer(lambda: set_active(True))
102 102
103 103 set_active(False)
104 104 id_, params = build_data(self.apikey, 'test', args='xx')
105 105 response = api_call(self.app, params)
106 106 expected = 'Request from this user not allowed'
107 107 assert_error(id_, expected, given=response.body)
108 108
109 109 def test_api_args_is_null(self):
110 110 __, params = build_data(self.apikey, 'get_users', )
111 111 params = params.replace('"args": {}', '"args": null')
112 112 response = api_call(self.app, params)
113 113 assert response.status == '200 OK'
114 114
115 115 def test_api_args_is_bad(self):
116 116 __, params = build_data(self.apikey, 'get_users', )
117 117 params = params.replace('"args": {}', '"args": 1')
118 118 response = api_call(self.app, params)
119 119 assert response.status == '200 OK'
120 120
121 121 def test_api_args_different_args(self):
122 122 import string
123 123 expected = {
124 124 'ascii_letters': string.ascii_letters,
125 125 'ws': string.whitespace,
126 126 'printables': string.printable
127 127 }
128 128 id_, params = build_data(self.apikey, 'test', args=expected)
129 129 response = api_call(self.app, params)
130 130 assert response.status == '200 OK'
131 131 assert_ok(id_, expected, response.body)
@@ -1,44 +1,44 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2017-2018 RhodeCode GmbH
3 # Copyright (C) 2017-2019 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,113 +1,113 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import UserLog
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 TestClosePullRequest(object):
32 32
33 33 @pytest.mark.backends("git", "hg")
34 34 def test_api_close_pull_request(self, pr_util):
35 35 pull_request = pr_util.create_pull_request()
36 36 pull_request_id = pull_request.pull_request_id
37 37 author = pull_request.user_id
38 38 repo = pull_request.target_repo.repo_id
39 39 id_, params = build_data(
40 40 self.apikey, 'close_pull_request',
41 41 repoid=pull_request.target_repo.repo_name,
42 42 pullrequestid=pull_request.pull_request_id)
43 43 response = api_call(self.app, params)
44 44 expected = {
45 45 'pull_request_id': pull_request_id,
46 46 'close_status': 'Rejected',
47 47 'closed': True,
48 48 }
49 49 assert_ok(id_, expected, response.body)
50 50 journal = UserLog.query()\
51 51 .filter(UserLog.user_id == author) \
52 52 .order_by('user_log_id') \
53 53 .filter(UserLog.repository_id == repo)\
54 54 .all()
55 55 assert journal[-1].action == 'repo.pull_request.close'
56 56
57 57 @pytest.mark.backends("git", "hg")
58 58 def test_api_close_pull_request_already_closed_error(self, pr_util):
59 59 pull_request = pr_util.create_pull_request()
60 60 pull_request_id = pull_request.pull_request_id
61 61 pull_request_repo = pull_request.target_repo.repo_name
62 62 PullRequestModel().close_pull_request(
63 63 pull_request, pull_request.author)
64 64 id_, params = build_data(
65 65 self.apikey, 'close_pull_request',
66 66 repoid=pull_request_repo, pullrequestid=pull_request_id)
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'pull request `%s` is already closed' % pull_request_id
70 70 assert_error(id_, expected, given=response.body)
71 71
72 72 @pytest.mark.backends("git", "hg")
73 73 def test_api_close_pull_request_repo_error(self, pr_util):
74 74 pull_request = pr_util.create_pull_request()
75 75 id_, params = build_data(
76 76 self.apikey, 'close_pull_request',
77 77 repoid=666, pullrequestid=pull_request.pull_request_id)
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)
82 82
83 83 @pytest.mark.backends("git", "hg")
84 84 def test_api_close_pull_request_non_admin_with_userid_error(self,
85 85 pr_util):
86 86 pull_request = pr_util.create_pull_request()
87 87 id_, params = build_data(
88 88 self.apikey_regular, 'close_pull_request',
89 89 repoid=pull_request.target_repo.repo_name,
90 90 pullrequestid=pull_request.pull_request_id,
91 91 userid=TEST_USER_ADMIN_LOGIN)
92 92 response = api_call(self.app, params)
93 93
94 94 expected = 'userid is not the same as your user'
95 95 assert_error(id_, expected, given=response.body)
96 96
97 97 @pytest.mark.backends("git", "hg")
98 98 def test_api_close_pull_request_no_perms_to_close(
99 99 self, user_util, pr_util):
100 100 user = user_util.create_user()
101 101 pull_request = pr_util.create_pull_request()
102 102
103 103 id_, params = build_data(
104 104 user.api_key, 'close_pull_request',
105 105 repoid=pull_request.target_repo.repo_name,
106 106 pullrequestid=pull_request.pull_request_id,)
107 107 response = api_call(self.app, params)
108 108
109 109 expected = ('pull request `%s` close failed, '
110 110 'no permission to close.') % pull_request.pull_request_id
111 111
112 112 response_json = response.json['error']
113 113 assert response_json == expected
@@ -1,81 +1,81 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import ChangesetStatus
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_error, assert_ok)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestCommentCommit(object):
30 30 def test_api_comment_commit_on_empty_repo(self, backend):
31 31 repo = backend.create_repo()
32 32 id_, params = build_data(
33 33 self.apikey, 'comment_commit', repoid=repo.repo_name,
34 34 commit_id='tip', message='message', status_change=None)
35 35 response = api_call(self.app, params)
36 36 expected = 'There are no commits yet'
37 37 assert_error(id_, expected, given=response.body)
38 38
39 39 @pytest.mark.parametrize("commit_id, expected_err", [
40 40 ('abcabca', {'hg': 'Commit {commit} does not exist for {repo}',
41 41 'git': 'Commit {commit} does not exist for {repo}',
42 42 'svn': 'Commit id {commit} not understood.'}),
43 43 ('idontexist', {'hg': 'Commit {commit} does not exist for {repo}',
44 44 'git': 'Commit {commit} does not exist for {repo}',
45 45 'svn': 'Commit id {commit} not understood.'}),
46 46 ])
47 47 def test_api_comment_commit_wrong_hash(self, backend, commit_id, expected_err):
48 48 repo_name = backend.repo.repo_name
49 49 id_, params = build_data(
50 50 self.apikey, 'comment_commit', repoid=repo_name,
51 51 commit_id=commit_id, message='message', status_change=None)
52 52 response = api_call(self.app, params)
53 53
54 54 expected_err = expected_err[backend.alias]
55 55 expected_err = expected_err.format(
56 56 repo=backend.repo.scm_instance(), commit=commit_id)
57 57 assert_error(id_, expected_err, given=response.body)
58 58
59 59 @pytest.mark.parametrize("status_change, message, commit_id", [
60 60 (None, 'Hallo', 'tip'),
61 61 (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'),
62 62 (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'),
63 63 ])
64 64 def test_api_comment_commit(
65 65 self, backend, status_change, message, commit_id,
66 66 no_notifications):
67 67
68 68 commit_id = backend.repo.scm_instance().get_commit(commit_id).raw_id
69 69
70 70 id_, params = build_data(
71 71 self.apikey, 'comment_commit', repoid=backend.repo_name,
72 72 commit_id=commit_id, message=message, status=status_change)
73 73 response = api_call(self.app, params)
74 74 repo = backend.repo.scm_instance()
75 75 expected = {
76 76 'msg': 'Commented on commit `%s` for repository `%s`' % (
77 77 repo.get_commit().raw_id, backend.repo_name),
78 78 'status_change': status_change,
79 79 'success': True
80 80 }
81 81 assert_ok(id_, expected, given=response.body)
@@ -1,209 +1,209 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.comment import CommentsModel
24 24 from rhodecode.model.db import UserLog
25 25 from rhodecode.model.pull_request import PullRequestModel
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_error, assert_ok)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestCommentPullRequest(object):
33 33 finalizers = []
34 34
35 35 def teardown_method(self, method):
36 36 if self.finalizers:
37 37 for finalizer in self.finalizers:
38 38 finalizer()
39 39 self.finalizers = []
40 40
41 41 @pytest.mark.backends("git", "hg")
42 42 def test_api_comment_pull_request(self, pr_util, no_notifications):
43 43 pull_request = pr_util.create_pull_request()
44 44 pull_request_id = pull_request.pull_request_id
45 45 author = pull_request.user_id
46 46 repo = pull_request.target_repo.repo_id
47 47 id_, params = build_data(
48 48 self.apikey, 'comment_pull_request',
49 49 repoid=pull_request.target_repo.repo_name,
50 50 pullrequestid=pull_request.pull_request_id,
51 51 message='test message')
52 52 response = api_call(self.app, params)
53 53 pull_request = PullRequestModel().get(pull_request.pull_request_id)
54 54
55 55 comments = CommentsModel().get_comments(
56 56 pull_request.target_repo.repo_id, pull_request=pull_request)
57 57
58 58 expected = {
59 59 'pull_request_id': pull_request.pull_request_id,
60 60 'comment_id': comments[-1].comment_id,
61 61 'status': {'given': None, 'was_changed': None}
62 62 }
63 63 assert_ok(id_, expected, response.body)
64 64
65 65 journal = UserLog.query()\
66 66 .filter(UserLog.user_id == author)\
67 67 .filter(UserLog.repository_id == repo) \
68 68 .order_by('user_log_id') \
69 69 .all()
70 70 assert journal[-1].action == 'repo.pull_request.comment.create'
71 71
72 72 @pytest.mark.backends("git", "hg")
73 73 def test_api_comment_pull_request_change_status(
74 74 self, pr_util, no_notifications):
75 75 pull_request = pr_util.create_pull_request()
76 76 pull_request_id = pull_request.pull_request_id
77 77 id_, params = build_data(
78 78 self.apikey, 'comment_pull_request',
79 79 repoid=pull_request.target_repo.repo_name,
80 80 pullrequestid=pull_request.pull_request_id,
81 81 status='rejected')
82 82 response = api_call(self.app, params)
83 83 pull_request = PullRequestModel().get(pull_request_id)
84 84
85 85 comments = CommentsModel().get_comments(
86 86 pull_request.target_repo.repo_id, pull_request=pull_request)
87 87 expected = {
88 88 'pull_request_id': pull_request.pull_request_id,
89 89 'comment_id': comments[-1].comment_id,
90 90 'status': {'given': 'rejected', 'was_changed': True}
91 91 }
92 92 assert_ok(id_, expected, response.body)
93 93
94 94 @pytest.mark.backends("git", "hg")
95 95 def test_api_comment_pull_request_change_status_with_specific_commit_id(
96 96 self, pr_util, no_notifications):
97 97 pull_request = pr_util.create_pull_request()
98 98 pull_request_id = pull_request.pull_request_id
99 99 latest_commit_id = 'test_commit'
100 100 # inject additional revision, to fail test the status change on
101 101 # non-latest commit
102 102 pull_request.revisions = pull_request.revisions + ['test_commit']
103 103
104 104 id_, params = build_data(
105 105 self.apikey, 'comment_pull_request',
106 106 repoid=pull_request.target_repo.repo_name,
107 107 pullrequestid=pull_request.pull_request_id,
108 108 status='approved', commit_id=latest_commit_id)
109 109 response = api_call(self.app, params)
110 110 pull_request = PullRequestModel().get(pull_request_id)
111 111
112 112 expected = {
113 113 'pull_request_id': pull_request.pull_request_id,
114 114 'comment_id': None,
115 115 'status': {'given': 'approved', 'was_changed': False}
116 116 }
117 117 assert_ok(id_, expected, response.body)
118 118
119 119 @pytest.mark.backends("git", "hg")
120 120 def test_api_comment_pull_request_change_status_with_specific_commit_id(
121 121 self, pr_util, no_notifications):
122 122 pull_request = pr_util.create_pull_request()
123 123 pull_request_id = pull_request.pull_request_id
124 124 latest_commit_id = pull_request.revisions[0]
125 125
126 126 id_, params = build_data(
127 127 self.apikey, 'comment_pull_request',
128 128 repoid=pull_request.target_repo.repo_name,
129 129 pullrequestid=pull_request.pull_request_id,
130 130 status='approved', commit_id=latest_commit_id)
131 131 response = api_call(self.app, params)
132 132 pull_request = PullRequestModel().get(pull_request_id)
133 133
134 134 comments = CommentsModel().get_comments(
135 135 pull_request.target_repo.repo_id, pull_request=pull_request)
136 136 expected = {
137 137 'pull_request_id': pull_request.pull_request_id,
138 138 'comment_id': comments[-1].comment_id,
139 139 'status': {'given': 'approved', 'was_changed': True}
140 140 }
141 141 assert_ok(id_, expected, response.body)
142 142
143 143 @pytest.mark.backends("git", "hg")
144 144 def test_api_comment_pull_request_missing_params_error(self, pr_util):
145 145 pull_request = pr_util.create_pull_request()
146 146 pull_request_id = pull_request.pull_request_id
147 147 pull_request_repo = pull_request.target_repo.repo_name
148 148 id_, params = build_data(
149 149 self.apikey, 'comment_pull_request',
150 150 repoid=pull_request_repo,
151 151 pullrequestid=pull_request_id)
152 152 response = api_call(self.app, params)
153 153
154 154 expected = 'Both message and status parameters are missing. At least one is required.'
155 155 assert_error(id_, expected, given=response.body)
156 156
157 157 @pytest.mark.backends("git", "hg")
158 158 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
159 159 pull_request = pr_util.create_pull_request()
160 160 pull_request_id = pull_request.pull_request_id
161 161 pull_request_repo = pull_request.target_repo.repo_name
162 162 id_, params = build_data(
163 163 self.apikey, 'comment_pull_request',
164 164 repoid=pull_request_repo,
165 165 pullrequestid=pull_request_id,
166 166 status='42')
167 167 response = api_call(self.app, params)
168 168
169 169 expected = 'Unknown comment status: `42`'
170 170 assert_error(id_, expected, given=response.body)
171 171
172 172 @pytest.mark.backends("git", "hg")
173 173 def test_api_comment_pull_request_repo_error(self, pr_util):
174 174 pull_request = pr_util.create_pull_request()
175 175 id_, params = build_data(
176 176 self.apikey, 'comment_pull_request',
177 177 repoid=666, pullrequestid=pull_request.pull_request_id)
178 178 response = api_call(self.app, params)
179 179
180 180 expected = 'repository `666` does not exist'
181 181 assert_error(id_, expected, given=response.body)
182 182
183 183 @pytest.mark.backends("git", "hg")
184 184 def test_api_comment_pull_request_non_admin_with_userid_error(
185 185 self, pr_util):
186 186 pull_request = pr_util.create_pull_request()
187 187 id_, params = build_data(
188 188 self.apikey_regular, 'comment_pull_request',
189 189 repoid=pull_request.target_repo.repo_name,
190 190 pullrequestid=pull_request.pull_request_id,
191 191 userid=TEST_USER_ADMIN_LOGIN)
192 192 response = api_call(self.app, params)
193 193
194 194 expected = 'userid is not the same as your user'
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_wrong_commit_id_error(self, pr_util):
199 199 pull_request = pr_util.create_pull_request()
200 200 id_, params = build_data(
201 201 self.apikey_regular, 'comment_pull_request',
202 202 repoid=pull_request.target_repo.repo_name,
203 203 status='approved',
204 204 pullrequestid=pull_request.pull_request_id,
205 205 commit_id='XXX')
206 206 response = api_call(self.app, params)
207 207
208 208 expected = 'Invalid commit_id `XXX` for this pull request.'
209 209 assert_error(id_, expected, given=response.body)
@@ -1,102 +1,102 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.db import Gist
25 25 from rhodecode.model.gist import GistModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error, assert_ok, crash)
28 28 from rhodecode.tests.fixture import Fixture
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestApiCreateGist(object):
33 33 @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [
34 34 (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC),
35 35 (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE),
36 36 (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC),
37 37 (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE),
38 38 ])
39 39 def test_api_create_gist(self, lifetime, gist_type, gist_acl_level):
40 40 id_, params = build_data(
41 41 self.apikey_regular, 'create_gist',
42 42 lifetime=lifetime,
43 43 description='foobar-gist',
44 44 gist_type=gist_type,
45 45 acl_level=gist_acl_level,
46 46 files={'foobar_ąć': {'content': 'foo'}})
47 47 response = api_call(self.app, params)
48 48 response_json = response.json
49 49 gist = response_json['result']['gist']
50 50 expected = {
51 51 'gist': {
52 52 'access_id': gist['access_id'],
53 53 'created_on': gist['created_on'],
54 54 'modified_at': gist['modified_at'],
55 55 'description': 'foobar-gist',
56 56 'expires': gist['expires'],
57 57 'gist_id': gist['gist_id'],
58 58 'type': gist_type,
59 59 'url': gist['url'],
60 60 # content is empty since we don't show it here
61 61 'content': None,
62 62 'acl_level': gist_acl_level,
63 63 },
64 64 'msg': 'created new gist'
65 65 }
66 66 try:
67 67 assert_ok(id_, expected, given=response.body)
68 68 finally:
69 69 Fixture().destroy_gists()
70 70
71 71 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
72 72 ({'gist_type': '"ups" is not one of private, public'},
73 73 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
74 74
75 75 ({'lifetime': '-120 is less than minimum value -1'},
76 76 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
77 77
78 78 ({'0.content': 'Required'},
79 79 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
80 80 ])
81 81 def test_api_try_create_gist(
82 82 self, expected, lifetime, gist_type, gist_acl_level, files):
83 83 id_, params = build_data(
84 84 self.apikey_regular, 'create_gist',
85 85 lifetime=lifetime,
86 86 description='foobar-gist',
87 87 gist_type=gist_type,
88 88 acl_level=gist_acl_level,
89 89 files=files)
90 90 response = api_call(self.app, params)
91 91
92 92 try:
93 93 assert_error(id_, expected, given=response.body)
94 94 finally:
95 95 Fixture().destroy_gists()
96 96
97 97 @mock.patch.object(GistModel, 'create', crash)
98 98 def test_api_create_gist_exception_occurred(self):
99 99 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
100 100 response = api_call(self.app, params)
101 101 expected = 'failed to create gist'
102 102 assert_error(id_, expected, given=response.body)
@@ -1,368 +1,368 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import User
24 24 from rhodecode.model.pull_request import PullRequestModel
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, TEST_USER_REGULAR_LOGIN
28 28 from rhodecode.api.tests.utils import build_data, api_call, assert_error
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestCreatePullRequestApi(object):
33 33 finalizers = []
34 34
35 35 def teardown_method(self, method):
36 36 if self.finalizers:
37 37 for finalizer in self.finalizers:
38 38 finalizer()
39 39 self.finalizers = []
40 40
41 41 def test_create_with_wrong_data(self):
42 42 required_data = {
43 43 'source_repo': 'tests/source_repo',
44 44 'target_repo': 'tests/target_repo',
45 45 'source_ref': 'branch:default:initial',
46 46 'target_ref': 'branch:default:new-feature',
47 47 }
48 48 for key in required_data:
49 49 data = required_data.copy()
50 50 data.pop(key)
51 51 id_, params = build_data(
52 52 self.apikey, 'create_pull_request', **data)
53 53 response = api_call(self.app, params)
54 54
55 55 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
56 56 assert_error(id_, expected, given=response.body)
57 57
58 58 @pytest.mark.backends("git", "hg")
59 59 @pytest.mark.parametrize('source_ref', [
60 60 'bookmarg:default:initial'
61 61 ])
62 62 def test_create_with_wrong_refs_data(self, backend, source_ref):
63 63
64 64 data = self._prepare_data(backend)
65 65 data['source_ref'] = source_ref
66 66
67 67 id_, params = build_data(
68 68 self.apikey_regular, 'create_pull_request', **data)
69 69
70 70 response = api_call(self.app, params)
71 71
72 72 expected = "Ref `{}` type is not allowed. " \
73 73 "Only:['bookmark', 'book', 'tag', 'branch'] " \
74 74 "are possible.".format(source_ref)
75 75 assert_error(id_, expected, given=response.body)
76 76
77 77 @pytest.mark.backends("git", "hg")
78 78 def test_create_with_correct_data(self, backend):
79 79 data = self._prepare_data(backend)
80 80 RepoModel().revoke_user_permission(
81 81 self.source.repo_name, User.DEFAULT_USER)
82 82 id_, params = build_data(
83 83 self.apikey_regular, 'create_pull_request', **data)
84 84 response = api_call(self.app, params)
85 85 expected_message = "Created new pull request `{title}`".format(
86 86 title=data['title'])
87 87 result = response.json
88 88 assert result['error'] is None
89 89 assert result['result']['msg'] == expected_message
90 90 pull_request_id = result['result']['pull_request_id']
91 91 pull_request = PullRequestModel().get(pull_request_id)
92 92 assert pull_request.title == data['title']
93 93 assert pull_request.description == data['description']
94 94 assert pull_request.source_ref == data['source_ref']
95 95 assert pull_request.target_ref == data['target_ref']
96 96 assert pull_request.source_repo.repo_name == data['source_repo']
97 97 assert pull_request.target_repo.repo_name == data['target_repo']
98 98 assert pull_request.revisions == [self.commit_ids['change']]
99 99 assert len(pull_request.reviewers) == 1
100 100
101 101 @pytest.mark.backends("git", "hg")
102 102 def test_create_with_empty_description(self, backend):
103 103 data = self._prepare_data(backend)
104 104 data.pop('description')
105 105 id_, params = build_data(
106 106 self.apikey_regular, 'create_pull_request', **data)
107 107 response = api_call(self.app, params)
108 108 expected_message = "Created new pull request `{title}`".format(
109 109 title=data['title'])
110 110 result = response.json
111 111 assert result['error'] is None
112 112 assert result['result']['msg'] == expected_message
113 113 pull_request_id = result['result']['pull_request_id']
114 114 pull_request = PullRequestModel().get(pull_request_id)
115 115 assert pull_request.description == ''
116 116
117 117 @pytest.mark.backends("git", "hg")
118 118 def test_create_with_empty_title(self, backend):
119 119 data = self._prepare_data(backend)
120 120 data.pop('title')
121 121 id_, params = build_data(
122 122 self.apikey_regular, 'create_pull_request', **data)
123 123 response = api_call(self.app, params)
124 124 result = response.json
125 125 pull_request_id = result['result']['pull_request_id']
126 126 pull_request = PullRequestModel().get(pull_request_id)
127 127 data['ref'] = backend.default_branch_name
128 128 title = '{source_repo}#{ref} to {target_repo}'.format(**data)
129 129 assert pull_request.title == title
130 130
131 131 @pytest.mark.backends("git", "hg")
132 132 def test_create_with_reviewers_specified_by_names(
133 133 self, backend, no_notifications):
134 134 data = self._prepare_data(backend)
135 135 reviewers = [
136 136 {'username': TEST_USER_REGULAR_LOGIN,
137 137 'reasons': ['{} added manually'.format(TEST_USER_REGULAR_LOGIN)]},
138 138 {'username': TEST_USER_ADMIN_LOGIN,
139 139 'reasons': ['{} added manually'.format(TEST_USER_ADMIN_LOGIN)],
140 140 'mandatory': True},
141 141 ]
142 142 data['reviewers'] = reviewers
143 143
144 144 id_, params = build_data(
145 145 self.apikey_regular, 'create_pull_request', **data)
146 146 response = api_call(self.app, params)
147 147
148 148 expected_message = "Created new pull request `{title}`".format(
149 149 title=data['title'])
150 150 result = response.json
151 151 assert result['error'] is None
152 152 assert result['result']['msg'] == expected_message
153 153 pull_request_id = result['result']['pull_request_id']
154 154 pull_request = PullRequestModel().get(pull_request_id)
155 155
156 156 actual_reviewers = []
157 157 for rev in pull_request.reviewers:
158 158 entry = {
159 159 'username': rev.user.username,
160 160 'reasons': rev.reasons,
161 161 }
162 162 if rev.mandatory:
163 163 entry['mandatory'] = rev.mandatory
164 164 actual_reviewers.append(entry)
165 165
166 166 owner_username = pull_request.target_repo.user.username
167 167 for spec_reviewer in reviewers[::]:
168 168 # default reviewer will be added who is an owner of the repo
169 169 # this get's overridden by a add owner to reviewers rule
170 170 if spec_reviewer['username'] == owner_username:
171 171 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
172 172 # since owner is more important, we don't inherit mandatory flag
173 173 del spec_reviewer['mandatory']
174 174
175 175 assert sorted(actual_reviewers, key=lambda e: e['username']) \
176 176 == sorted(reviewers, key=lambda e: e['username'])
177 177
178 178 @pytest.mark.backends("git", "hg")
179 179 def test_create_with_reviewers_specified_by_ids(
180 180 self, backend, no_notifications):
181 181 data = self._prepare_data(backend)
182 182 reviewers = [
183 183 {'username': UserModel().get_by_username(
184 184 TEST_USER_REGULAR_LOGIN).user_id,
185 185 'reasons': ['added manually']},
186 186 {'username': UserModel().get_by_username(
187 187 TEST_USER_ADMIN_LOGIN).user_id,
188 188 'reasons': ['added manually']},
189 189 ]
190 190
191 191 data['reviewers'] = reviewers
192 192 id_, params = build_data(
193 193 self.apikey_regular, 'create_pull_request', **data)
194 194 response = api_call(self.app, params)
195 195
196 196 expected_message = "Created new pull request `{title}`".format(
197 197 title=data['title'])
198 198 result = response.json
199 199 assert result['error'] is None
200 200 assert result['result']['msg'] == expected_message
201 201 pull_request_id = result['result']['pull_request_id']
202 202 pull_request = PullRequestModel().get(pull_request_id)
203 203
204 204 actual_reviewers = []
205 205 for rev in pull_request.reviewers:
206 206 entry = {
207 207 'username': rev.user.user_id,
208 208 'reasons': rev.reasons,
209 209 }
210 210 if rev.mandatory:
211 211 entry['mandatory'] = rev.mandatory
212 212 actual_reviewers.append(entry)
213 213
214 214 owner_user_id = pull_request.target_repo.user.user_id
215 215 for spec_reviewer in reviewers[::]:
216 216 # default reviewer will be added who is an owner of the repo
217 217 # this get's overridden by a add owner to reviewers rule
218 218 if spec_reviewer['username'] == owner_user_id:
219 219 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
220 220
221 221 assert sorted(actual_reviewers, key=lambda e: e['username']) \
222 222 == sorted(reviewers, key=lambda e: e['username'])
223 223
224 224 @pytest.mark.backends("git", "hg")
225 225 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
226 226 data = self._prepare_data(backend)
227 227 data['reviewers'] = [{'username': 'somebody'}]
228 228 id_, params = build_data(
229 229 self.apikey_regular, 'create_pull_request', **data)
230 230 response = api_call(self.app, params)
231 231 expected_message = 'user `somebody` does not exist'
232 232 assert_error(id_, expected_message, given=response.body)
233 233
234 234 @pytest.mark.backends("git", "hg")
235 235 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
236 236 data = self._prepare_data(backend)
237 237 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
238 238 data['reviewers'] = reviewers
239 239 id_, params = build_data(
240 240 self.apikey_regular, 'create_pull_request', **data)
241 241 response = api_call(self.app, params)
242 242 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
243 243 assert_error(id_, expected_message, given=response.body)
244 244
245 245 @pytest.mark.backends("git", "hg")
246 246 def test_create_with_no_commit_hashes(self, backend):
247 247 data = self._prepare_data(backend)
248 248 expected_source_ref = data['source_ref']
249 249 expected_target_ref = data['target_ref']
250 250 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
251 251 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
252 252 id_, params = build_data(
253 253 self.apikey_regular, 'create_pull_request', **data)
254 254 response = api_call(self.app, params)
255 255 expected_message = "Created new pull request `{title}`".format(
256 256 title=data['title'])
257 257 result = response.json
258 258 assert result['result']['msg'] == expected_message
259 259 pull_request_id = result['result']['pull_request_id']
260 260 pull_request = PullRequestModel().get(pull_request_id)
261 261 assert pull_request.source_ref == expected_source_ref
262 262 assert pull_request.target_ref == expected_target_ref
263 263
264 264 @pytest.mark.backends("git", "hg")
265 265 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
266 266 def test_create_fails_with_wrong_repo(self, backend, data_key):
267 267 repo_name = 'fake-repo'
268 268 data = self._prepare_data(backend)
269 269 data[data_key] = repo_name
270 270 id_, params = build_data(
271 271 self.apikey_regular, 'create_pull_request', **data)
272 272 response = api_call(self.app, params)
273 273 expected_message = 'repository `{}` does not exist'.format(repo_name)
274 274 assert_error(id_, expected_message, given=response.body)
275 275
276 276 @pytest.mark.backends("git", "hg")
277 277 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
278 278 def test_create_fails_with_non_existing_branch(self, backend, data_key):
279 279 branch_name = 'test-branch'
280 280 data = self._prepare_data(backend)
281 281 data[data_key] = "branch:{}".format(branch_name)
282 282 id_, params = build_data(
283 283 self.apikey_regular, 'create_pull_request', **data)
284 284 response = api_call(self.app, params)
285 285 expected_message = 'The specified value:{type}:`{name}` ' \
286 286 'does not exist, or is not allowed.'.format(type='branch',
287 287 name=branch_name)
288 288 assert_error(id_, expected_message, given=response.body)
289 289
290 290 @pytest.mark.backends("git", "hg")
291 291 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
292 292 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
293 293 data = self._prepare_data(backend)
294 294 ref = 'stange-ref'
295 295 data[data_key] = ref
296 296 id_, params = build_data(
297 297 self.apikey_regular, 'create_pull_request', **data)
298 298 response = api_call(self.app, params)
299 299 expected_message = (
300 300 'Ref `{ref}` given in a wrong format. Please check the API'
301 301 ' documentation for more details'.format(ref=ref))
302 302 assert_error(id_, expected_message, given=response.body)
303 303
304 304 @pytest.mark.backends("git", "hg")
305 305 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
306 306 def test_create_fails_with_non_existing_ref(self, backend, data_key):
307 307 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
308 308 ref = self._get_full_ref(backend, commit_id)
309 309 data = self._prepare_data(backend)
310 310 data[data_key] = ref
311 311 id_, params = build_data(
312 312 self.apikey_regular, 'create_pull_request', **data)
313 313 response = api_call(self.app, params)
314 314 expected_message = 'Ref `{}` does not exist'.format(ref)
315 315 assert_error(id_, expected_message, given=response.body)
316 316
317 317 @pytest.mark.backends("git", "hg")
318 318 def test_create_fails_when_no_revisions(self, backend):
319 319 data = self._prepare_data(backend, source_head='initial')
320 320 id_, params = build_data(
321 321 self.apikey_regular, 'create_pull_request', **data)
322 322 response = api_call(self.app, params)
323 323 expected_message = 'no commits found'
324 324 assert_error(id_, expected_message, given=response.body)
325 325
326 326 @pytest.mark.backends("git", "hg")
327 327 def test_create_fails_when_no_permissions(self, backend):
328 328 data = self._prepare_data(backend)
329 329 RepoModel().revoke_user_permission(
330 330 self.source.repo_name, self.test_user)
331 331 RepoModel().revoke_user_permission(
332 332 self.source.repo_name, User.DEFAULT_USER)
333 333
334 334 id_, params = build_data(
335 335 self.apikey_regular, 'create_pull_request', **data)
336 336 response = api_call(self.app, params)
337 337 expected_message = 'repository `{}` does not exist'.format(
338 338 self.source.repo_name)
339 339 assert_error(id_, expected_message, given=response.body)
340 340
341 341 def _prepare_data(
342 342 self, backend, source_head='change', target_head='initial'):
343 343 commits = [
344 344 {'message': 'initial'},
345 345 {'message': 'change'},
346 346 {'message': 'new-feature', 'parents': ['initial']},
347 347 ]
348 348 self.commit_ids = backend.create_master_repo(commits)
349 349 self.source = backend.create_repo(heads=[source_head])
350 350 self.target = backend.create_repo(heads=[target_head])
351 351
352 352 data = {
353 353 'source_repo': self.source.repo_name,
354 354 'target_repo': self.target.repo_name,
355 355 'source_ref': self._get_full_ref(
356 356 backend, self.commit_ids[source_head]),
357 357 'target_ref': self._get_full_ref(
358 358 backend, self.commit_ids[target_head]),
359 359 'title': 'Test PR 1',
360 360 'description': 'Test'
361 361 }
362 362 RepoModel().grant_user_permission(
363 363 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
364 364 return data
365 365
366 366 def _get_full_ref(self, backend, commit_id):
367 367 return 'branch:{branch}:{commit_id}'.format(
368 368 branch=backend.default_branch_name, commit_id=commit_id)
@@ -1,350 +1,350 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 json
22 22
23 23 import mock
24 24 import pytest
25 25
26 26 from rhodecode.lib.utils2 import safe_unicode
27 27 from rhodecode.lib.vcs import settings
28 28 from rhodecode.model.meta import Session
29 29 from rhodecode.model.repo import RepoModel
30 30 from rhodecode.model.user import UserModel
31 31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
32 32 from rhodecode.api.tests.utils import (
33 33 build_data, api_call, assert_ok, assert_error, crash)
34 34 from rhodecode.tests.fixture import Fixture
35 35
36 36
37 37 fixture = Fixture()
38 38
39 39
40 40 @pytest.mark.usefixtures("testuser_api", "app")
41 41 class TestCreateRepo(object):
42 42
43 43 @pytest.mark.parametrize('given, expected_name, expected_exc', [
44 44 ('api repo-1', 'api-repo-1', False),
45 45 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
46 46 (u'unicode-ąć', u'unicode-ąć', False),
47 47 ('some repo v1.2', 'some-repo-v1.2', False),
48 48 ('v2.0', 'v2.0', False),
49 49 ])
50 50 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
51 51
52 52 id_, params = build_data(
53 53 self.apikey,
54 54 'create_repo',
55 55 repo_name=given,
56 56 owner=TEST_USER_ADMIN_LOGIN,
57 57 repo_type=backend.alias,
58 58 )
59 59 response = api_call(self.app, params)
60 60
61 61 ret = {
62 62 'msg': 'Created new repository `%s`' % (expected_name,),
63 63 'success': True,
64 64 'task': None,
65 65 }
66 66 expected = ret
67 67 assert_ok(id_, expected, given=response.body)
68 68
69 69 repo = RepoModel().get_by_repo_name(safe_unicode(expected_name))
70 70 assert repo is not None
71 71
72 72 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
73 73 response = api_call(self.app, params)
74 74 body = json.loads(response.body)
75 75
76 76 assert body['result']['enable_downloads'] is False
77 77 assert body['result']['enable_locking'] is False
78 78 assert body['result']['enable_statistics'] is False
79 79
80 80 fixture.destroy_repo(safe_unicode(expected_name))
81 81
82 82 def test_api_create_restricted_repo_type(self, backend):
83 83 repo_name = 'api-repo-type-{0}'.format(backend.alias)
84 84 id_, params = build_data(
85 85 self.apikey,
86 86 'create_repo',
87 87 repo_name=repo_name,
88 88 owner=TEST_USER_ADMIN_LOGIN,
89 89 repo_type=backend.alias,
90 90 )
91 91 git_backend = settings.BACKENDS['git']
92 92 with mock.patch(
93 93 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
94 94 response = api_call(self.app, params)
95 95
96 96 repo = RepoModel().get_by_repo_name(repo_name)
97 97
98 98 if backend.alias == 'git':
99 99 assert repo is not None
100 100 expected = {
101 101 'msg': 'Created new repository `{0}`'.format(repo_name,),
102 102 'success': True,
103 103 'task': None,
104 104 }
105 105 assert_ok(id_, expected, given=response.body)
106 106 else:
107 107 assert repo is None
108 108
109 109 fixture.destroy_repo(repo_name)
110 110
111 111 def test_api_create_repo_with_booleans(self, backend):
112 112 repo_name = 'api-repo-2'
113 113 id_, params = build_data(
114 114 self.apikey,
115 115 'create_repo',
116 116 repo_name=repo_name,
117 117 owner=TEST_USER_ADMIN_LOGIN,
118 118 repo_type=backend.alias,
119 119 enable_statistics=True,
120 120 enable_locking=True,
121 121 enable_downloads=True
122 122 )
123 123 response = api_call(self.app, params)
124 124
125 125 repo = RepoModel().get_by_repo_name(repo_name)
126 126
127 127 assert repo is not None
128 128 ret = {
129 129 'msg': 'Created new repository `%s`' % (repo_name,),
130 130 'success': True,
131 131 'task': None,
132 132 }
133 133 expected = ret
134 134 assert_ok(id_, expected, given=response.body)
135 135
136 136 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
137 137 response = api_call(self.app, params)
138 138 body = json.loads(response.body)
139 139
140 140 assert body['result']['enable_downloads'] is True
141 141 assert body['result']['enable_locking'] is True
142 142 assert body['result']['enable_statistics'] is True
143 143
144 144 fixture.destroy_repo(repo_name)
145 145
146 146 def test_api_create_repo_in_group(self, backend):
147 147 repo_group_name = 'my_gr'
148 148 # create the parent
149 149 fixture.create_repo_group(repo_group_name)
150 150
151 151 repo_name = '%s/api-repo-gr' % (repo_group_name,)
152 152 id_, params = build_data(
153 153 self.apikey, 'create_repo',
154 154 repo_name=repo_name,
155 155 owner=TEST_USER_ADMIN_LOGIN,
156 156 repo_type=backend.alias,)
157 157 response = api_call(self.app, params)
158 158 repo = RepoModel().get_by_repo_name(repo_name)
159 159 assert repo is not None
160 160 assert repo.group is not None
161 161
162 162 ret = {
163 163 'msg': 'Created new repository `%s`' % (repo_name,),
164 164 'success': True,
165 165 'task': None,
166 166 }
167 167 expected = ret
168 168 assert_ok(id_, expected, given=response.body)
169 169 fixture.destroy_repo(repo_name)
170 170 fixture.destroy_repo_group(repo_group_name)
171 171
172 172 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
173 173 repo_group_name = 'fake_group'
174 174
175 175 repo_name = '%s/api-repo-gr' % (repo_group_name,)
176 176 id_, params = build_data(
177 177 self.apikey, 'create_repo',
178 178 repo_name=repo_name,
179 179 owner=TEST_USER_ADMIN_LOGIN,
180 180 repo_type=backend.alias,)
181 181 response = api_call(self.app, params)
182 182
183 183 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
184 184 repo_group_name)}
185 185 assert_error(id_, expected, given=response.body)
186 186
187 187 def test_api_create_repo_unknown_owner(self, backend):
188 188 repo_name = 'api-repo-2'
189 189 owner = 'i-dont-exist'
190 190 id_, params = build_data(
191 191 self.apikey, 'create_repo',
192 192 repo_name=repo_name,
193 193 owner=owner,
194 194 repo_type=backend.alias)
195 195 response = api_call(self.app, params)
196 196 expected = 'user `%s` does not exist' % (owner,)
197 197 assert_error(id_, expected, given=response.body)
198 198
199 199 def test_api_create_repo_dont_specify_owner(self, backend):
200 200 repo_name = 'api-repo-3'
201 201 id_, params = build_data(
202 202 self.apikey, 'create_repo',
203 203 repo_name=repo_name,
204 204 repo_type=backend.alias)
205 205 response = api_call(self.app, params)
206 206
207 207 repo = RepoModel().get_by_repo_name(repo_name)
208 208 assert repo is not None
209 209 ret = {
210 210 'msg': 'Created new repository `%s`' % (repo_name,),
211 211 'success': True,
212 212 'task': None,
213 213 }
214 214 expected = ret
215 215 assert_ok(id_, expected, given=response.body)
216 216 fixture.destroy_repo(repo_name)
217 217
218 218 def test_api_create_repo_by_non_admin(self, backend):
219 219 repo_name = 'api-repo-4'
220 220 id_, params = build_data(
221 221 self.apikey_regular, 'create_repo',
222 222 repo_name=repo_name,
223 223 repo_type=backend.alias)
224 224 response = api_call(self.app, params)
225 225
226 226 repo = RepoModel().get_by_repo_name(repo_name)
227 227 assert repo is not None
228 228 ret = {
229 229 'msg': 'Created new repository `%s`' % (repo_name,),
230 230 'success': True,
231 231 'task': None,
232 232 }
233 233 expected = ret
234 234 assert_ok(id_, expected, given=response.body)
235 235 fixture.destroy_repo(repo_name)
236 236
237 237 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
238 238 repo_name = 'api-repo-5'
239 239 owner = 'i-dont-exist'
240 240 id_, params = build_data(
241 241 self.apikey_regular, 'create_repo',
242 242 repo_name=repo_name,
243 243 repo_type=backend.alias,
244 244 owner=owner)
245 245 response = api_call(self.app, params)
246 246
247 247 expected = 'Only RhodeCode super-admin can specify `owner` param'
248 248 assert_error(id_, expected, given=response.body)
249 249 fixture.destroy_repo(repo_name)
250 250
251 251 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
252 252 repo_group_name = 'no-access'
253 253 fixture.create_repo_group(repo_group_name)
254 254 repo_name = 'no-access/api-repo'
255 255
256 256 id_, params = build_data(
257 257 self.apikey_regular, 'create_repo',
258 258 repo_name=repo_name,
259 259 repo_type=backend.alias)
260 260 response = api_call(self.app, params)
261 261
262 262 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
263 263 repo_group_name)}
264 264 assert_error(id_, expected, given=response.body)
265 265 fixture.destroy_repo_group(repo_group_name)
266 266 fixture.destroy_repo(repo_name)
267 267
268 268 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
269 269 self, backend, user_util):
270 270
271 271 regular_user = user_util.create_user()
272 272 regular_user_api_key = regular_user.api_key
273 273
274 274 usr = UserModel().get_by_username(regular_user.username)
275 275 usr.inherit_default_permissions = False
276 276 Session().add(usr)
277 277
278 278 repo_name = backend.new_repo_name()
279 279 id_, params = build_data(
280 280 regular_user_api_key, 'create_repo',
281 281 repo_name=repo_name,
282 282 repo_type=backend.alias)
283 283 response = api_call(self.app, params)
284 284 expected = {
285 285 "repo_name": "You do not have the permission to "
286 286 "store repositories in the root location."}
287 287 assert_error(id_, expected, given=response.body)
288 288
289 289 def test_api_create_repo_exists(self, backend):
290 290 repo_name = backend.repo_name
291 291 id_, params = build_data(
292 292 self.apikey, 'create_repo',
293 293 repo_name=repo_name,
294 294 owner=TEST_USER_ADMIN_LOGIN,
295 295 repo_type=backend.alias,)
296 296 response = api_call(self.app, params)
297 297 expected = {
298 298 'unique_repo_name': 'Repository with name `{}` already exists'.format(
299 299 repo_name)}
300 300 assert_error(id_, expected, given=response.body)
301 301
302 302 @mock.patch.object(RepoModel, 'create', crash)
303 303 def test_api_create_repo_exception_occurred(self, backend):
304 304 repo_name = 'api-repo-6'
305 305 id_, params = build_data(
306 306 self.apikey, 'create_repo',
307 307 repo_name=repo_name,
308 308 owner=TEST_USER_ADMIN_LOGIN,
309 309 repo_type=backend.alias,)
310 310 response = api_call(self.app, params)
311 311 expected = 'failed to create repository `%s`' % (repo_name,)
312 312 assert_error(id_, expected, given=response.body)
313 313
314 314 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
315 315 (None, 'foo bar x', 'foo-bar-x'),
316 316 ('foo', '/foo//bar x', 'foo/bar-x'),
317 317 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
318 318 ])
319 319 def test_create_repo_with_extra_slashes_in_name(
320 320 self, backend, parent_group, dirty_name, expected_name):
321 321
322 322 if parent_group:
323 323 gr = fixture.create_repo_group(parent_group)
324 324 assert gr.group_name == parent_group
325 325
326 326 id_, params = build_data(
327 327 self.apikey, 'create_repo',
328 328 repo_name=dirty_name,
329 329 repo_type=backend.alias,
330 330 owner=TEST_USER_ADMIN_LOGIN,)
331 331 response = api_call(self.app, params)
332 332 expected ={
333 333 "msg": "Created new repository `{}`".format(expected_name),
334 334 "task": None,
335 335 "success": True
336 336 }
337 337 assert_ok(id_, expected, response.body)
338 338
339 339 repo = RepoModel().get_by_repo_name(expected_name)
340 340 assert repo is not None
341 341
342 342 expected = {
343 343 'msg': 'Created new repository `%s`' % (expected_name,),
344 344 'success': True,
345 345 'task': None,
346 346 }
347 347 assert_ok(id_, expected, given=response.body)
348 348 fixture.destroy_repo(expected_name)
349 349 if parent_group:
350 350 fixture.destroy_repo_group(parent_group)
@@ -1,289 +1,289 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.meta import Session
25 25 from rhodecode.model.repo_group import RepoGroupModel
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
32 32
33 33 fixture = Fixture()
34 34
35 35
36 36 @pytest.mark.usefixtures("testuser_api", "app")
37 37 class TestCreateRepoGroup(object):
38 38 def test_api_create_repo_group(self):
39 39 repo_group_name = 'api-repo-group'
40 40
41 41 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
42 42 assert repo_group is None
43 43
44 44 id_, params = build_data(
45 45 self.apikey, 'create_repo_group',
46 46 group_name=repo_group_name,
47 47 owner=TEST_USER_ADMIN_LOGIN,)
48 48 response = api_call(self.app, params)
49 49
50 50 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
51 51 assert repo_group is not None
52 52 ret = {
53 53 'msg': 'Created new repo group `%s`' % (repo_group_name,),
54 54 'repo_group': repo_group.get_api_data()
55 55 }
56 56 expected = ret
57 57 try:
58 58 assert_ok(id_, expected, given=response.body)
59 59 finally:
60 60 fixture.destroy_repo_group(repo_group_name)
61 61
62 62 def test_api_create_repo_group_in_another_group(self):
63 63 repo_group_name = 'api-repo-group'
64 64
65 65 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
66 66 assert repo_group is None
67 67 # create the parent
68 68 fixture.create_repo_group(repo_group_name)
69 69
70 70 full_repo_group_name = repo_group_name+'/'+repo_group_name
71 71 id_, params = build_data(
72 72 self.apikey, 'create_repo_group',
73 73 group_name=full_repo_group_name,
74 74 owner=TEST_USER_ADMIN_LOGIN,
75 75 copy_permissions=True)
76 76 response = api_call(self.app, params)
77 77
78 78 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
79 79 assert repo_group is not None
80 80 ret = {
81 81 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
82 82 'repo_group': repo_group.get_api_data()
83 83 }
84 84 expected = ret
85 85 try:
86 86 assert_ok(id_, expected, given=response.body)
87 87 finally:
88 88 fixture.destroy_repo_group(full_repo_group_name)
89 89 fixture.destroy_repo_group(repo_group_name)
90 90
91 91 def test_api_create_repo_group_in_another_group_not_existing(self):
92 92 repo_group_name = 'api-repo-group-no'
93 93
94 94 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
95 95 assert repo_group is None
96 96
97 97 full_repo_group_name = repo_group_name+'/'+repo_group_name
98 98 id_, params = build_data(
99 99 self.apikey, 'create_repo_group',
100 100 group_name=full_repo_group_name,
101 101 owner=TEST_USER_ADMIN_LOGIN,
102 102 copy_permissions=True)
103 103 response = api_call(self.app, params)
104 104 expected = {
105 105 'repo_group':
106 106 'Parent repository group `{}` does not exist'.format(
107 107 repo_group_name)}
108 108 assert_error(id_, expected, given=response.body)
109 109
110 110 def test_api_create_repo_group_that_exists(self):
111 111 repo_group_name = 'api-repo-group'
112 112
113 113 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
114 114 assert repo_group is None
115 115
116 116 fixture.create_repo_group(repo_group_name)
117 117 id_, params = build_data(
118 118 self.apikey, 'create_repo_group',
119 119 group_name=repo_group_name,
120 120 owner=TEST_USER_ADMIN_LOGIN,)
121 121 response = api_call(self.app, params)
122 122 expected = {
123 123 'unique_repo_group_name':
124 124 'Repository group with name `{}` already exists'.format(
125 125 repo_group_name)}
126 126 try:
127 127 assert_error(id_, expected, given=response.body)
128 128 finally:
129 129 fixture.destroy_repo_group(repo_group_name)
130 130
131 131 def test_api_create_repo_group_regular_user_wit_root_location_perms(
132 132 self, user_util):
133 133 regular_user = user_util.create_user()
134 134 regular_user_api_key = regular_user.api_key
135 135
136 136 repo_group_name = 'api-repo-group-by-regular-user'
137 137
138 138 usr = UserModel().get_by_username(regular_user.username)
139 139 usr.inherit_default_permissions = False
140 140 Session().add(usr)
141 141
142 142 UserModel().grant_perm(
143 143 regular_user.username, 'hg.repogroup.create.true')
144 144 Session().commit()
145 145
146 146 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
147 147 assert repo_group is None
148 148
149 149 id_, params = build_data(
150 150 regular_user_api_key, 'create_repo_group',
151 151 group_name=repo_group_name)
152 152 response = api_call(self.app, params)
153 153
154 154 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
155 155 assert repo_group is not None
156 156 expected = {
157 157 'msg': 'Created new repo group `%s`' % (repo_group_name,),
158 158 'repo_group': repo_group.get_api_data()
159 159 }
160 160 try:
161 161 assert_ok(id_, expected, given=response.body)
162 162 finally:
163 163 fixture.destroy_repo_group(repo_group_name)
164 164
165 165 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
166 166 self, user_util):
167 167
168 168 repo_group_name = 'api-repo-group-parent'
169 169
170 170 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
171 171 assert repo_group is None
172 172 # create the parent
173 173 fixture.create_repo_group(repo_group_name)
174 174
175 175 # user perms
176 176 regular_user = user_util.create_user()
177 177 regular_user_api_key = regular_user.api_key
178 178
179 179 usr = UserModel().get_by_username(regular_user.username)
180 180 usr.inherit_default_permissions = False
181 181 Session().add(usr)
182 182
183 183 RepoGroupModel().grant_user_permission(
184 184 repo_group_name, regular_user.username, 'group.admin')
185 185 Session().commit()
186 186
187 187 full_repo_group_name = repo_group_name + '/' + repo_group_name
188 188 id_, params = build_data(
189 189 regular_user_api_key, 'create_repo_group',
190 190 group_name=full_repo_group_name)
191 191 response = api_call(self.app, params)
192 192
193 193 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
194 194 assert repo_group is not None
195 195 expected = {
196 196 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
197 197 'repo_group': repo_group.get_api_data()
198 198 }
199 199 try:
200 200 assert_ok(id_, expected, given=response.body)
201 201 finally:
202 202 fixture.destroy_repo_group(full_repo_group_name)
203 203 fixture.destroy_repo_group(repo_group_name)
204 204
205 205 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
206 206 repo_group_name = 'api-repo-group'
207 207
208 208 id_, params = build_data(
209 209 self.apikey_regular, 'create_repo_group',
210 210 group_name=repo_group_name)
211 211 response = api_call(self.app, params)
212 212
213 213 expected = {
214 214 'repo_group':
215 215 u'You do not have the permission to store '
216 216 u'repository groups in the root location.'}
217 217 assert_error(id_, expected, given=response.body)
218 218
219 219 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
220 220 repo_group_name = 'api-repo-group-regular-user'
221 221
222 222 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
223 223 assert repo_group is None
224 224 # create the parent
225 225 fixture.create_repo_group(repo_group_name)
226 226
227 227 full_repo_group_name = repo_group_name+'/'+repo_group_name
228 228
229 229 id_, params = build_data(
230 230 self.apikey_regular, 'create_repo_group',
231 231 group_name=full_repo_group_name)
232 232 response = api_call(self.app, params)
233 233
234 234 expected = {
235 235 'repo_group':
236 236 'Parent repository group `{}` does not exist'.format(
237 237 repo_group_name)}
238 238 try:
239 239 assert_error(id_, expected, given=response.body)
240 240 finally:
241 241 fixture.destroy_repo_group(repo_group_name)
242 242
243 243 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
244 244 self):
245 245 repo_group_name = 'api-repo-group'
246 246
247 247 id_, params = build_data(
248 248 self.apikey_regular, 'create_repo_group',
249 249 group_name=repo_group_name,
250 250 owner=TEST_USER_ADMIN_LOGIN,)
251 251 response = api_call(self.app, params)
252 252
253 253 expected = "Only RhodeCode super-admin can specify `owner` param"
254 254 assert_error(id_, expected, given=response.body)
255 255
256 256 @mock.patch.object(RepoGroupModel, 'create', crash)
257 257 def test_api_create_repo_group_exception_occurred(self):
258 258 repo_group_name = 'api-repo-group'
259 259
260 260 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
261 261 assert repo_group is None
262 262
263 263 id_, params = build_data(
264 264 self.apikey, 'create_repo_group',
265 265 group_name=repo_group_name,
266 266 owner=TEST_USER_ADMIN_LOGIN,)
267 267 response = api_call(self.app, params)
268 268 expected = 'failed to create repo group `%s`' % (repo_group_name,)
269 269 assert_error(id_, expected, given=response.body)
270 270
271 271 def test_create_group_with_extra_slashes_in_name(self, user_util):
272 272 existing_repo_group = user_util.create_repo_group()
273 273 dirty_group_name = '//{}//group2//'.format(
274 274 existing_repo_group.group_name)
275 275 cleaned_group_name = '{}/group2'.format(
276 276 existing_repo_group.group_name)
277 277
278 278 id_, params = build_data(
279 279 self.apikey, 'create_repo_group',
280 280 group_name=dirty_group_name,
281 281 owner=TEST_USER_ADMIN_LOGIN,)
282 282 response = api_call(self.app, params)
283 283 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
284 284 expected = {
285 285 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
286 286 'repo_group': repo_group.get_api_data()
287 287 }
288 288 assert_ok(id_, expected, given=response.body)
289 289 fixture.destroy_repo_group(cleaned_group_name)
@@ -1,206 +1,206 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.auth import check_password
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.tests import (
27 27 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
28 28 from rhodecode.api.tests.utils import (
29 29 build_data, api_call, assert_ok, assert_error, jsonify, crash)
30 30 from rhodecode.tests.fixture import Fixture
31 31 from rhodecode.model.db import RepoGroup
32 32
33 33
34 34 # TODO: mikhail: remove fixture from here
35 35 fixture = Fixture()
36 36
37 37
38 38 @pytest.mark.usefixtures("testuser_api", "app")
39 39 class TestCreateUser(object):
40 40 def test_api_create_existing_user(self):
41 41 id_, params = build_data(
42 42 self.apikey, 'create_user',
43 43 username=TEST_USER_ADMIN_LOGIN,
44 44 email='test@foo.com',
45 45 password='trololo')
46 46 response = api_call(self.app, params)
47 47
48 48 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
49 49 assert_error(id_, expected, given=response.body)
50 50
51 51 def test_api_create_user_with_existing_email(self):
52 52 id_, params = build_data(
53 53 self.apikey, 'create_user',
54 54 username=TEST_USER_ADMIN_LOGIN + 'new',
55 55 email=TEST_USER_REGULAR_EMAIL,
56 56 password='trololo')
57 57 response = api_call(self.app, params)
58 58
59 59 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
60 60 assert_error(id_, expected, given=response.body)
61 61
62 62 def test_api_create_user_with_wrong_username(self):
63 63 bad_username = '<> HELLO WORLD <>'
64 64 id_, params = build_data(
65 65 self.apikey, 'create_user',
66 66 username=bad_username,
67 67 email='new@email.com',
68 68 password='trololo')
69 69 response = api_call(self.app, params)
70 70
71 71 expected = {'username':
72 72 "Username may only contain alphanumeric characters "
73 73 "underscores, periods or dashes and must begin with "
74 74 "alphanumeric character or underscore"}
75 75 assert_error(id_, expected, given=response.body)
76 76
77 77 def test_api_create_user(self):
78 78 username = 'test_new_api_user'
79 79 email = username + "@foo.com"
80 80
81 81 id_, params = build_data(
82 82 self.apikey, 'create_user',
83 83 username=username,
84 84 email=email,
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,127 +1,127 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.meta import Session
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.model.user_group import UserGroupModel
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29 29 from rhodecode.tests.fixture import Fixture
30 30
31 31
32 32 @pytest.mark.usefixtures("testuser_api", "app")
33 33 class TestCreateUserGroup(object):
34 34 fixture = Fixture()
35 35
36 36 def test_api_create_user_group(self):
37 37 group_name = 'some_new_group'
38 38 id_, params = build_data(
39 39 self.apikey, 'create_user_group', group_name=group_name)
40 40 response = api_call(self.app, params)
41 41
42 42 ret = {
43 43 'msg': 'created new user group `%s`' % (group_name,),
44 44 'user_group': jsonify(
45 45 UserGroupModel()
46 46 .get_by_name(group_name)
47 47 .get_api_data()
48 48 )
49 49 }
50 50 expected = ret
51 51 assert_ok(id_, expected, given=response.body)
52 52 self.fixture.destroy_user_group(group_name)
53 53
54 54 def test_api_create_user_group_regular_user(self):
55 55 group_name = 'some_new_group'
56 56
57 57 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
58 58 usr.inherit_default_permissions = False
59 59 Session().add(usr)
60 60 UserModel().grant_perm(
61 61 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
62 62 Session().commit()
63 63
64 64 id_, params = build_data(
65 65 self.apikey_regular, 'create_user_group', group_name=group_name)
66 66 response = api_call(self.app, params)
67 67
68 68 expected = {
69 69 'msg': 'created new user group `%s`' % (group_name,),
70 70 'user_group': jsonify(
71 71 UserGroupModel()
72 72 .get_by_name(group_name)
73 73 .get_api_data()
74 74 )
75 75 }
76 76 try:
77 77 assert_ok(id_, expected, given=response.body)
78 78 finally:
79 79 self.fixture.destroy_user_group(group_name)
80 80 UserModel().revoke_perm(
81 81 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
82 82 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
83 83 usr.inherit_default_permissions = True
84 84 Session().add(usr)
85 85 Session().commit()
86 86
87 87 def test_api_create_user_group_regular_user_no_permission(self):
88 88 group_name = 'some_new_group'
89 89 id_, params = build_data(
90 90 self.apikey_regular, 'create_user_group', group_name=group_name)
91 91 response = api_call(self.app, params)
92 92 expected = "Access was denied to this resource."
93 93 assert_error(id_, expected, given=response.body)
94 94
95 95 def test_api_create_user_group_that_exist(self, user_util):
96 96 group = user_util.create_user_group()
97 97 group_name = group.users_group_name
98 98
99 99 id_, params = build_data(
100 100 self.apikey, 'create_user_group', group_name=group_name)
101 101 response = api_call(self.app, params)
102 102
103 103 expected = "user group `%s` already exist" % (group_name,)
104 104 assert_error(id_, expected, given=response.body)
105 105
106 106 @mock.patch.object(UserGroupModel, 'create', crash)
107 107 def test_api_create_user_group_exception_occurred(self):
108 108 group_name = 'exception_happens'
109 109 id_, params = build_data(
110 110 self.apikey, 'create_user_group', group_name=group_name)
111 111 response = api_call(self.app, params)
112 112
113 113 expected = 'failed to create group `%s`' % (group_name,)
114 114 assert_error(id_, expected, given=response.body)
115 115
116 116 def test_api_create_user_group_with_wrong_name(self, user_util):
117 117
118 118 group_name = 'wrong NAME <>'
119 119 id_, params = build_data(
120 120 self.apikey, 'create_user_group', group_name=group_name)
121 121 response = api_call(self.app, params)
122 122
123 123 expected = {"user_group_name":
124 124 "Allowed in name are letters, numbers, and `-`, `_`, "
125 125 "`.` Name must start with a letter or number. "
126 126 "Got `{}`".format(group_name)}
127 127 assert_error(id_, expected, given=response.body)
@@ -1,61 +1,61 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.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
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiDeleteGist(object):
31 31 def test_api_delete_gist(self, gist_util):
32 32 gist_id = gist_util.create_gist().gist_access_id
33 33 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
34 34 response = api_call(self.app, params)
35 35 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
36 36 assert_ok(id_, expected, given=response.body)
37 37
38 38 def test_api_delete_gist_regular_user(self, gist_util):
39 39 gist_id = gist_util.create_gist(
40 40 owner=self.TEST_USER_LOGIN).gist_access_id
41 41 id_, params = build_data(
42 42 self.apikey_regular, 'delete_gist', gistid=gist_id)
43 43 response = api_call(self.app, params)
44 44 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
45 45 assert_ok(id_, expected, given=response.body)
46 46
47 47 def test_api_delete_gist_regular_user_no_permission(self, gist_util):
48 48 gist_id = gist_util.create_gist().gist_access_id
49 49 id_, params = build_data(
50 50 self.apikey_regular, 'delete_gist', gistid=gist_id)
51 51 response = api_call(self.app, params)
52 52 expected = 'gist `%s` does not exist' % (gist_id,)
53 53 assert_error(id_, expected, given=response.body)
54 54
55 55 @mock.patch.object(GistModel, 'delete', crash)
56 56 def test_api_delete_gist_exception_occurred(self, gist_util):
57 57 gist_id = gist_util.create_gist().gist_access_id
58 58 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
59 59 response = api_call(self.app, params)
60 60 expected = 'failed to delete gist ID:%s' % (gist_id,)
61 61 assert_error(id_, expected, given=response.body)
@@ -1,74 +1,74 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.repo import RepoModel
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 TestApiDeleteRepo(object):
31 31 def test_api_delete_repo(self, backend):
32 32 repo = backend.create_repo()
33 33 repo_name = repo.repo_name
34 34 id_, params = build_data(
35 35 self.apikey, 'delete_repo', repoid=repo.repo_name, )
36 36 response = api_call(self.app, params)
37 37
38 38 expected = {
39 39 'msg': 'Deleted repository `%s`' % (repo_name,),
40 40 'success': True
41 41 }
42 42 assert_ok(id_, expected, given=response.body)
43 43
44 44 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
45 45 repo = backend.create_repo(cur_user=user_regular.username)
46 46 repo_name = repo.repo_name
47 47 id_, params = build_data(
48 48 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
49 49 response = api_call(self.app, params)
50 50
51 51 expected = {
52 52 'msg': 'Deleted repository `%s`' % (repo_name,),
53 53 'success': True
54 54 }
55 55 assert_ok(id_, expected, given=response.body)
56 56
57 57 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
58 58 repo = backend.create_repo()
59 59 repo_name = repo.repo_name
60 60 id_, params = build_data(
61 61 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
62 62 response = api_call(self.app, params)
63 63 expected = 'repository `%s` does not exist' % (repo_name)
64 64 assert_error(id_, expected, given=response.body)
65 65
66 66 def test_api_delete_repo_exception_occurred(self, backend):
67 67 repo = backend.create_repo()
68 68 repo_name = repo.repo_name
69 69 id_, params = build_data(
70 70 self.apikey, 'delete_repo', repoid=repo.repo_name, )
71 71 with mock.patch.object(RepoModel, 'delete', crash):
72 72 response = api_call(self.app, params)
73 73 expected = 'failed to delete repository `%s`' % (repo_name,)
74 74 assert_error(id_, expected, given=response.body)
@@ -1,86 +1,86 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 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 TestApiDeleteRepoGroup(object):
32 32 def test_api_delete_repo_group(self, user_util):
33 33 repo_group = user_util.create_repo_group(auto_cleanup=False)
34 34 repo_group_name = repo_group.group_name
35 35 repo_group_id = repo_group.group_id
36 36 id_, params = build_data(
37 37 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
38 38 response = api_call(self.app, params)
39 39
40 40 ret = {
41 41 'msg': 'deleted repo group ID:%s %s' % (
42 42 repo_group_id, repo_group_name
43 43 ),
44 44 'repo_group': None
45 45 }
46 46 expected = ret
47 47 assert_ok(id_, expected, given=response.body)
48 48 gr = RepoGroupModel()._get_repo_group(repo_group_name)
49 49 assert gr is None
50 50
51 51 def test_api_delete_repo_group_regular_user(self, user_util):
52 52 repo_group = user_util.create_repo_group(auto_cleanup=False)
53 53 repo_group_name = repo_group.group_name
54 54 repo_group_id = repo_group.group_id
55 55
56 56 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 57 user_util.grant_user_permission_to_repo_group(
58 58 repo_group, user, 'group.admin')
59 59
60 60 id_, params = build_data(
61 61 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
62 62 response = api_call(self.app, params)
63 63
64 64 ret = {
65 65 'msg': 'deleted repo group ID:%s %s' % (
66 66 repo_group_id, repo_group_name
67 67 ),
68 68 'repo_group': None
69 69 }
70 70 expected = ret
71 71 assert_ok(id_, expected, given=response.body)
72 72 gr = RepoGroupModel()._get_repo_group(repo_group_name)
73 73 assert gr is None
74 74
75 75 def test_api_delete_repo_group_regular_user_no_permission(self, user_util):
76 76 repo_group = user_util.create_repo_group()
77 77 repo_group_name = repo_group.group_name
78 78
79 79 id_, params = build_data(
80 80 self.apikey_regular, 'delete_repo_group',
81 81 repogroupid=repo_group_name, )
82 82 response = api_call(self.app, params)
83 83
84 84 expected = 'repository group `%s` does not exist' % (
85 85 repo_group_name,)
86 86 assert_error(id_, expected, given=response.body)
@@ -1,57 +1,57 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
23 23 import pytest
24 24
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_ok, assert_error, crash)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestDeleteUser(object):
32 32 def test_api_delete_user(self, user_util):
33 33 usr = user_util.create_user(auto_cleanup=False)
34 34
35 35 username = usr.username
36 36 usr_id = usr.user_id
37 37
38 38 id_, params = build_data(self.apikey, 'delete_user', userid=username)
39 39 response = api_call(self.app, params)
40 40
41 41 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
42 42 'user': None}
43 43 expected = ret
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 @mock.patch.object(UserModel, 'delete', crash)
47 47 def test_api_delete_user_when_exception_happened(self, user_util):
48 48 usr = user_util.create_user()
49 49 username = usr.username
50 50
51 51 id_, params = build_data(
52 52 self.apikey, 'delete_user', userid=username, )
53 53 response = api_call(self.app, params)
54 54 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
55 55 usr.username)
56 56 expected = ret
57 57 assert_error(id_, expected, given=response.body)
@@ -1,106 +1,106 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
23 23 import pytest
24 24
25 25 from rhodecode.model.user import UserModel
26 26 from rhodecode.model.user_group import UserGroupModel
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok, crash)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestDeleteUserGroup(object):
33 33 def test_api_delete_user_group(self, user_util):
34 34 user_group = user_util.create_user_group(auto_cleanup=False)
35 35 group_name = user_group.users_group_name
36 36 group_id = user_group.users_group_id
37 37 id_, params = build_data(
38 38 self.apikey, 'delete_user_group', usergroupid=group_name)
39 39 response = api_call(self.app, params)
40 40
41 41 expected = {
42 42 'user_group': None,
43 43 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
44 44 }
45 45 assert_ok(id_, expected, given=response.body)
46 46
47 47 def test_api_delete_user_group_regular_user(self, user_util):
48 48 ugroup = user_util.create_user_group(auto_cleanup=False)
49 49 group_name = ugroup.users_group_name
50 50 group_id = ugroup.users_group_id
51 51 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
52 52
53 53 user_util.grant_user_permission_to_user_group(
54 54 ugroup, user, 'usergroup.admin')
55 55
56 56 id_, params = build_data(
57 57 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
58 58 response = api_call(self.app, params)
59 59
60 60 expected = {
61 61 'user_group': None,
62 62 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
63 63 }
64 64 assert_ok(id_, expected, given=response.body)
65 65
66 66 def test_api_delete_user_group_regular_user_no_permission(self, user_util):
67 67 user_group = user_util.create_user_group()
68 68 group_name = user_group.users_group_name
69 69
70 70 id_, params = build_data(
71 71 self.apikey_regular, 'delete_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)
76 76
77 77 def test_api_delete_user_group_that_is_assigned(self, backend, user_util):
78 78 ugroup = user_util.create_user_group()
79 79 group_name = ugroup.users_group_name
80 80 repo = backend.create_repo()
81 81
82 82 ugr_to_perm = user_util.grant_user_group_permission_to_repo(
83 83 repo, ugroup, 'repository.write')
84 84 msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository)
85 85
86 86 id_, params = build_data(
87 87 self.apikey, 'delete_user_group',
88 88 usergroupid=group_name)
89 89 response = api_call(self.app, params)
90 90
91 91 expected = msg
92 92 assert_error(id_, expected, given=response.body)
93 93
94 94 def test_api_delete_user_group_exception_occurred(self, user_util):
95 95 ugroup = user_util.create_user_group()
96 96 group_name = ugroup.users_group_name
97 97 group_id = ugroup.users_group_id
98 98 id_, params = build_data(
99 99 self.apikey, 'delete_user_group',
100 100 usergroupid=group_name)
101 101
102 102 with mock.patch.object(UserGroupModel, 'delete', crash):
103 103 response = api_call(self.app, params)
104 104 expected = 'failed to delete user group ID:%s %s' % (
105 105 group_id, group_name)
106 106 assert_error(id_, expected, given=response.body)
@@ -1,79 +1,79 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.api.views import deprecated_api
25 25 from rhodecode.lib.ext_json import json
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestCommitComment(object):
32 32 def test_deprecated_message_in_docstring(self):
33 33 docstring = deprecated_api.changeset_comment.__doc__
34 34 assert '.. deprecated:: 3.4.0' in docstring
35 35 assert 'Please use method `comment_commit` instead.' in docstring
36 36
37 37 def test_deprecated_message_in_retvalue(self):
38 38
39 39 id_, params = build_data(
40 40 self.apikey, 'show_ip')
41 41 response = api_call(self.app, params)
42 42
43 43 expected = {
44 44 'id': id_,
45 45 'error': None,
46 46 'result': json.loads(response.body)['result'],
47 47 'DEPRECATION_WARNING':
48 48 'DEPRECATED METHOD Please use method `get_ip` instead.'
49 49 }
50 50 assert expected == json.loads(response.body)
51 51
52 52 # def test_calls_comment_commit(self, backend, no_notifications):
53 53 # data = {
54 54 # 'repoid': backend.repo_name,
55 55 # 'status': ChangesetStatus.STATUS_APPROVED,
56 56 # 'message': 'Approved',
57 57 # 'revision': 'tip'
58 58 # }
59 59 # with patch.object(repo_api, 'changeset_commit') as comment_mock:
60 60 # id_, params = build_data(self.apikey, 'comment_commit', **data)
61 61 # api_call(self.app, params)
62 62 #
63 63 # _, call_args = comment_mock.call_args
64 64 # data['commit_id'] = data.pop('revision')
65 65 # for key in data:
66 66 # assert call_args[key] == data[key]
67 67
68 68 # def test_warning_log_contains_deprecation_message(self):
69 69 # api = self.SampleApi()
70 70 # with patch.object(utils, 'log') as log_mock:
71 71 # api.api_method()
72 72 #
73 73 # assert log_mock.warning.call_count == 1
74 74 # call_args = log_mock.warning.call_args[0]
75 75 # assert (
76 76 # call_args[0] ==
77 77 # 'DEPRECATED API CALL on function %s, please use `%s` instead')
78 78 # assert call_args[1].__name__ == 'api_method'
79 79 # assert call_args[2] == 'new_method' No newline at end of file
@@ -1,279 +1,279 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
23 23 import pytest
24 24
25 25 from rhodecode.model.meta import Session
26 26 from rhodecode.model.repo import RepoModel
27 27 from rhodecode.model.repo_group import RepoGroupModel
28 28 from rhodecode.model.user import UserModel
29 29 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
30 30 from rhodecode.api.tests.utils import (
31 31 build_data, api_call, assert_error, assert_ok, crash)
32 32 from rhodecode.tests.fixture import Fixture
33 33
34 34
35 35 fixture = Fixture()
36 36
37 37
38 38 @pytest.mark.usefixtures("testuser_api", "app")
39 39 class TestApiForkRepo(object):
40 40 def test_api_fork_repo(self, backend):
41 41 source_name = backend['minimal'].repo_name
42 42 fork_name = backend.new_repo_name()
43 43
44 44 id_, params = build_data(
45 45 self.apikey, 'fork_repo',
46 46 repoid=source_name,
47 47 fork_name=fork_name,
48 48 owner=TEST_USER_ADMIN_LOGIN)
49 49 response = api_call(self.app, params)
50 50
51 51 expected = {
52 52 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
53 53 'success': True,
54 54 'task': None,
55 55 }
56 56 try:
57 57 assert_ok(id_, expected, given=response.body)
58 58 finally:
59 59 fixture.destroy_repo(fork_name)
60 60
61 61 def test_api_fork_repo_into_group(self, backend, user_util):
62 62 source_name = backend['minimal'].repo_name
63 63 repo_group = user_util.create_repo_group()
64 64 fork_name = '%s/api-repo-fork' % repo_group.group_name
65 65 id_, params = build_data(
66 66 self.apikey, 'fork_repo',
67 67 repoid=source_name,
68 68 fork_name=fork_name,
69 69 owner=TEST_USER_ADMIN_LOGIN)
70 70 response = api_call(self.app, params)
71 71
72 72 ret = {
73 73 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
74 74 'success': True,
75 75 'task': None,
76 76 }
77 77 expected = ret
78 78 try:
79 79 assert_ok(id_, expected, given=response.body)
80 80 finally:
81 81 fixture.destroy_repo(fork_name)
82 82
83 83 def test_api_fork_repo_non_admin(self, backend):
84 84 source_name = backend['minimal'].repo_name
85 85 fork_name = backend.new_repo_name()
86 86
87 87 id_, params = build_data(
88 88 self.apikey_regular, 'fork_repo',
89 89 repoid=source_name,
90 90 fork_name=fork_name)
91 91 response = api_call(self.app, params)
92 92
93 93 expected = {
94 94 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
95 95 'success': True,
96 96 'task': None,
97 97 }
98 98 try:
99 99 assert_ok(id_, expected, given=response.body)
100 100 finally:
101 101 fixture.destroy_repo(fork_name)
102 102
103 103 def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util):
104 104 source_name = backend['minimal'].repo_name
105 105 repo_group = user_util.create_repo_group()
106 106 repo_group_name = repo_group.group_name
107 107 fork_name = '%s/api-repo-fork' % repo_group_name
108 108
109 109 id_, params = build_data(
110 110 self.apikey_regular, 'fork_repo',
111 111 repoid=source_name,
112 112 fork_name=fork_name)
113 113 response = api_call(self.app, params)
114 114
115 115 expected = {
116 116 'repo_group': 'Repository group `{}` does not exist'.format(
117 117 repo_group_name)}
118 118 try:
119 119 assert_error(id_, expected, given=response.body)
120 120 finally:
121 121 fixture.destroy_repo(fork_name)
122 122
123 123 def test_api_fork_repo_non_admin_into_group(self, backend, user_util):
124 124 source_name = backend['minimal'].repo_name
125 125 repo_group = user_util.create_repo_group()
126 126 fork_name = '%s/api-repo-fork' % repo_group.group_name
127 127
128 128 RepoGroupModel().grant_user_permission(
129 129 repo_group, self.TEST_USER_LOGIN, 'group.admin')
130 130 Session().commit()
131 131
132 132 id_, params = build_data(
133 133 self.apikey_regular, 'fork_repo',
134 134 repoid=source_name,
135 135 fork_name=fork_name)
136 136 response = api_call(self.app, params)
137 137
138 138 expected = {
139 139 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
140 140 'success': True,
141 141 'task': None,
142 142 }
143 143 try:
144 144 assert_ok(id_, expected, given=response.body)
145 145 finally:
146 146 fixture.destroy_repo(fork_name)
147 147
148 148 def test_api_fork_repo_non_admin_specify_owner(self, backend):
149 149 source_name = backend['minimal'].repo_name
150 150 fork_name = backend.new_repo_name()
151 151 id_, params = build_data(
152 152 self.apikey_regular, 'fork_repo',
153 153 repoid=source_name,
154 154 fork_name=fork_name,
155 155 owner=TEST_USER_ADMIN_LOGIN)
156 156 response = api_call(self.app, params)
157 157 expected = 'Only RhodeCode super-admin can specify `owner` param'
158 158 assert_error(id_, expected, given=response.body)
159 159
160 160 def test_api_fork_repo_non_admin_no_permission_of_source_repo(
161 161 self, backend):
162 162 source_name = backend['minimal'].repo_name
163 163 RepoModel().grant_user_permission(repo=source_name,
164 164 user=self.TEST_USER_LOGIN,
165 165 perm='repository.none')
166 166 fork_name = backend.new_repo_name()
167 167 id_, params = build_data(
168 168 self.apikey_regular, 'fork_repo',
169 169 repoid=backend.repo_name,
170 170 fork_name=fork_name)
171 171 response = api_call(self.app, params)
172 172 expected = 'repository `%s` does not exist' % (backend.repo_name)
173 173 assert_error(id_, expected, given=response.body)
174 174
175 175 def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level(
176 176 self, backend, user_util):
177 177
178 178 regular_user = user_util.create_user()
179 179 regular_user_api_key = regular_user.api_key
180 180 usr = UserModel().get_by_username(regular_user.username)
181 181 usr.inherit_default_permissions = False
182 182 Session().add(usr)
183 183 UserModel().grant_perm(regular_user.username, 'hg.fork.repository')
184 184
185 185 source_name = backend['minimal'].repo_name
186 186 fork_name = backend.new_repo_name()
187 187 id_, params = build_data(
188 188 regular_user_api_key, 'fork_repo',
189 189 repoid=source_name,
190 190 fork_name=fork_name)
191 191 response = api_call(self.app, params)
192 192 expected = {
193 193 "repo_name": "You do not have the permission to "
194 194 "store repositories in the root location."}
195 195 assert_error(id_, expected, given=response.body)
196 196
197 197 def test_api_fork_repo_non_admin_no_permission_to_fork(
198 198 self, backend, user_util):
199 199
200 200 regular_user = user_util.create_user()
201 201 regular_user_api_key = regular_user.api_key
202 202 usr = UserModel().get_by_username(regular_user.username)
203 203 usr.inherit_default_permissions = False
204 204 Session().add(usr)
205 205
206 206 source_name = backend['minimal'].repo_name
207 207 fork_name = backend.new_repo_name()
208 208 id_, params = build_data(
209 209 regular_user_api_key, 'fork_repo',
210 210 repoid=source_name,
211 211 fork_name=fork_name)
212 212 response = api_call(self.app, params)
213 213
214 214 expected = "Access was denied to this resource."
215 215 assert_error(id_, expected, given=response.body)
216 216
217 217 def test_api_fork_repo_unknown_owner(self, backend):
218 218 source_name = backend['minimal'].repo_name
219 219 fork_name = backend.new_repo_name()
220 220 owner = 'i-dont-exist'
221 221 id_, params = build_data(
222 222 self.apikey, 'fork_repo',
223 223 repoid=source_name,
224 224 fork_name=fork_name,
225 225 owner=owner)
226 226 response = api_call(self.app, params)
227 227 expected = 'user `%s` does not exist' % (owner,)
228 228 assert_error(id_, expected, given=response.body)
229 229
230 230 def test_api_fork_repo_fork_exists(self, backend):
231 231 source_name = backend['minimal'].repo_name
232 232 fork_name = backend.new_repo_name()
233 233 fork_repo = fixture.create_fork(source_name, fork_name)
234 234
235 235 id_, params = build_data(
236 236 self.apikey, 'fork_repo',
237 237 repoid=source_name,
238 238 fork_name=fork_name,
239 239 owner=TEST_USER_ADMIN_LOGIN)
240 240 response = api_call(self.app, params)
241 241
242 242 try:
243 243 expected = {
244 244 'unique_repo_name': 'Repository with name `{}` already exists'.format(
245 245 fork_name)}
246 246 assert_error(id_, expected, given=response.body)
247 247 finally:
248 248 fixture.destroy_repo(fork_repo.repo_name)
249 249
250 250 def test_api_fork_repo_repo_exists(self, backend):
251 251 source_name = backend['minimal'].repo_name
252 252 fork_name = source_name
253 253
254 254 id_, params = build_data(
255 255 self.apikey, 'fork_repo',
256 256 repoid=source_name,
257 257 fork_name=fork_name,
258 258 owner=TEST_USER_ADMIN_LOGIN)
259 259 response = api_call(self.app, params)
260 260
261 261 expected = {
262 262 'unique_repo_name': 'Repository with name `{}` already exists'.format(
263 263 fork_name)}
264 264 assert_error(id_, expected, given=response.body)
265 265
266 266 @mock.patch.object(RepoModel, 'create_fork', crash)
267 267 def test_api_fork_repo_exception_occurred(self, backend):
268 268 source_name = backend['minimal'].repo_name
269 269 fork_name = backend.new_repo_name()
270 270 id_, params = build_data(
271 271 self.apikey, 'fork_repo',
272 272 repoid=source_name,
273 273 fork_name=fork_name,
274 274 owner=TEST_USER_ADMIN_LOGIN)
275 275 response = api_call(self.app, params)
276 276
277 277 expected = 'failed to fork repository `%s` as `%s`' % (source_name,
278 278 fork_name)
279 279 assert_error(id_, expected, given=response.body)
@@ -1,101 +1,101 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
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 u'filename1.txt': {'content': u'hello world'},
58 58 u'filename1ą.txt': {'content': u'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,74 +1,74 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
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)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiGetGist(object):
31 31 def test_api_get_gists(self, gist_util):
32 32 gist_util.create_gist()
33 33 gist_util.create_gist()
34 34
35 35 id_, params = build_data(self.apikey, 'get_gists')
36 36 response = api_call(self.app, params)
37 37 assert len(response.json['result']) == 2
38 38
39 39 def test_api_get_gists_regular_user(self, gist_util):
40 40 # by admin
41 41 gist_util.create_gist()
42 42 gist_util.create_gist()
43 43
44 44 # by reg user
45 45 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
46 46 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
47 47 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
48 48
49 49 id_, params = build_data(self.apikey_regular, 'get_gists')
50 50 response = api_call(self.app, params)
51 51 assert len(response.json['result']) == 3
52 52
53 53 def test_api_get_gists_only_for_regular_user(self, gist_util):
54 54 # by admin
55 55 gist_util.create_gist()
56 56 gist_util.create_gist()
57 57
58 58 # by reg user
59 59 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
60 60 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
61 61 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
62 62
63 63 id_, params = build_data(
64 64 self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN)
65 65 response = api_call(self.app, params)
66 66 assert len(response.json['result']) == 3
67 67
68 68 def test_api_get_gists_regular_user_with_different_userid(self):
69 69 id_, params = build_data(
70 70 self.apikey_regular, 'get_gists',
71 71 userid=TEST_USER_ADMIN_LOGIN)
72 72 response = api_call(self.app, params)
73 73 expected = 'userid is not the same as your user'
74 74 assert_error(id_, expected, given=response.body)
@@ -1,36 +1,36 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetIp(object):
29 29 def test_api_get_ip(self):
30 30 id_, params = build_data(self.apikey, 'get_ip')
31 31 response = api_call(self.app, params)
32 32 expected = {
33 33 'server_ip_addr': '0.0.0.0',
34 34 'user_ips': []
35 35 }
36 36 assert_ok(id_, expected, given=response.body)
@@ -1,91 +1,91 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.model.db import Repository, User
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_ok, assert_error)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetLocks(object):
32 32 def test_api_get_user_locks_regular_user(self):
33 33 id_, params = build_data(self.apikey_regular, 'get_user_locks')
34 34 response = api_call(self.app, params)
35 35 expected = []
36 36 assert_ok(id_, expected, given=response.body)
37 37
38 38 def test_api_get_user_locks_with_userid_regular_user(self):
39 39 id_, params = build_data(
40 40 self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN)
41 41 response = api_call(self.app, params)
42 42 expected = 'userid is not the same as your user'
43 43 assert_error(id_, expected, given=response.body)
44 44
45 45 def test_api_get_user_locks(self):
46 46 id_, params = build_data(self.apikey, 'get_user_locks')
47 47 response = api_call(self.app, params)
48 48 expected = []
49 49 assert_ok(id_, expected, given=response.body)
50 50
51 51 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
52 52 ('apikey', True),
53 53 ('apikey_regular', False),
54 54 ])
55 55 def test_api_get_user_locks_with_one_locked_repo(
56 56 self, apikey_attr, expect_secrets, backend):
57 57
58 58 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
59 59 Repository.lock(
60 60 repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
61 61
62 62 apikey = getattr(self, apikey_attr)
63 63
64 64 id_, params = build_data(apikey, 'get_user_locks')
65 65 if apikey_attr == 'apikey':
66 66 # super-admin should call in specific user
67 67 id_, params = build_data(apikey, 'get_user_locks',
68 68 userid=self.TEST_USER_LOGIN)
69 69
70 70 response = api_call(self.app, params)
71 71 expected = [repo.get_api_data(include_secrets=expect_secrets)]
72 72 assert_ok(id_, expected, given=response.body)
73 73
74 74 def test_api_get_user_locks_with_one_locked_repo_for_specific_user(
75 75 self, backend):
76 76 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
77 77
78 78 Repository.lock(repo, User.get_by_username(
79 79 self.TEST_USER_LOGIN).user_id)
80 80 id_, params = build_data(
81 81 self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN)
82 82 response = api_call(self.app, params)
83 83 expected = [repo.get_api_data(include_secrets=True)]
84 84 assert_ok(id_, expected, given=response.body)
85 85
86 86 def test_api_get_user_locks_with_userid(self):
87 87 id_, params = build_data(
88 88 self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN)
89 89 response = api_call(self.app, params)
90 90 expected = []
91 91 assert_ok(id_, expected, given=response.body)
@@ -1,59 +1,59 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetMethod(object):
29 29 def test_get_methods_no_matches(self):
30 30 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
31 31 response = api_call(self.app, params)
32 32
33 33 expected = []
34 34 assert_ok(id_, expected, given=response.body)
35 35
36 36 def test_get_methods(self):
37 37 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
38 38 response = api_call(self.app, params)
39 39
40 40 expected = ['changeset_comment', 'comment_pull_request',
41 41 'get_pull_request_comments', 'comment_commit']
42 42 assert_ok(id_, expected, given=response.body)
43 43
44 44 def test_get_methods_on_single_match(self):
45 45 id_, params = build_data(self.apikey, 'get_method',
46 46 pattern='*comment_commit*')
47 47 response = api_call(self.app, params)
48 48
49 49 expected = ['comment_commit',
50 50 {'apiuser': '<RequiredType>',
51 51 'comment_type': "<Optional:u'note'>",
52 52 'commit_id': '<RequiredType>',
53 53 'message': '<RequiredType>',
54 54 'repoid': '<RequiredType>',
55 55 'request': '<RequiredType>',
56 56 'resolves_comment_id': '<Optional:None>',
57 57 'status': '<Optional:None>',
58 58 'userid': '<Optional:<OptionalAttr:apiuser>>'}]
59 59 assert_ok(id_, expected, given=response.body)
@@ -1,142 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23 import urlobject
24 24
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import safe_unicode
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)
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_unicode(
54 54 url_obj.with_netloc(http_host_only_stub))
55 55 source_url = safe_unicode(
56 56 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
57 57 target_url = safe_unicode(
58 58 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
59 59 shadow_url = safe_unicode(
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 'created_on': pull_request.created_on,
69 69 'updated_on': pull_request.updated_on,
70 70 'commit_ids': pull_request.revisions,
71 71 'review_status': pull_request.calculated_review_status(),
72 72 'mergeable': {
73 73 'status': True,
74 74 'message': 'This pull request can be automatically merged.',
75 75 },
76 76 'source': {
77 77 'clone_url': source_url,
78 78 'repository': pull_request.source_repo.repo_name,
79 79 'reference': {
80 80 'name': pull_request.source_ref_parts.name,
81 81 'type': pull_request.source_ref_parts.type,
82 82 'commit_id': pull_request.source_ref_parts.commit_id,
83 83 },
84 84 },
85 85 'target': {
86 86 'clone_url': target_url,
87 87 'repository': pull_request.target_repo.repo_name,
88 88 'reference': {
89 89 'name': pull_request.target_ref_parts.name,
90 90 'type': pull_request.target_ref_parts.type,
91 91 'commit_id': pull_request.target_ref_parts.commit_id,
92 92 },
93 93 },
94 94 'merge': {
95 95 'clone_url': shadow_url,
96 96 'reference': {
97 97 'name': pull_request.shadow_merge_ref.name,
98 98 'type': pull_request.shadow_merge_ref.type,
99 99 'commit_id': pull_request.shadow_merge_ref.commit_id,
100 100 },
101 101 },
102 102 'author': pull_request.author.get_api_data(include_secrets=False,
103 103 details='basic'),
104 104 'reviewers': [
105 105 {
106 106 'user': reviewer.get_api_data(include_secrets=False,
107 107 details='basic'),
108 108 'reasons': reasons,
109 109 'review_status': st[0][1].status if st else 'not_reviewed',
110 110 }
111 111 for obj, reviewer, reasons, mandatory, st in
112 112 pull_request.reviewers_statuses()
113 113 ]
114 114 }
115 115 assert_ok(id_, expected, response.body)
116 116
117 117 def test_api_get_pull_request_repo_error(self, pr_util):
118 118 pull_request = pr_util.create_pull_request()
119 119 id_, params = build_data(
120 120 self.apikey, 'get_pull_request',
121 121 repoid=666, pullrequestid=pull_request.pull_request_id)
122 122 response = api_call(self.app, params)
123 123
124 124 expected = 'repository `666` does not exist'
125 125 assert_error(id_, expected, given=response.body)
126 126
127 127 def test_api_get_pull_request_pull_request_error(self):
128 128 id_, params = build_data(
129 129 self.apikey, 'get_pull_request', pullrequestid=666)
130 130 response = api_call(self.app, params)
131 131
132 132 expected = 'pull request `666` does not exist'
133 133 assert_error(id_, expected, given=response.body)
134 134
135 135 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
136 136 id_, params = build_data(
137 137 self.apikey, 'get_pull_request',
138 138 pullrequestid=666)
139 139 response = api_call(self.app, params)
140 140
141 141 expected = 'pull request `666` does not exist'
142 142 assert_error(id_, expected, given=response.body)
@@ -1,82 +1,82 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23 import urlobject
24 24
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_error, assert_ok)
27 27 from rhodecode.lib import helpers as h
28 28 from rhodecode.lib.utils2 import safe_unicode
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 TestGetPullRequestComments(object):
35 35
36 36 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
37 37 from rhodecode.model.pull_request import PullRequestModel
38 38
39 39 pull_request = pr_util.create_pull_request(mergeable=True)
40 40 id_, params = build_data(
41 41 self.apikey, 'get_pull_request_comments',
42 42 pullrequestid=pull_request.pull_request_id)
43 43
44 44 response = api_call(self.app, params)
45 45
46 46 assert response.status == '200 OK'
47 47 resp_date = response.json['result'][0]['comment_created_on']
48 48 resp_comment_id = response.json['result'][0]['comment_id']
49 49
50 50 expected = [
51 51 {'comment_author': {'active': True,
52 52 'full_name_or_username': 'RhodeCode Admin',
53 53 'username': 'test_admin'},
54 54 'comment_created_on': resp_date,
55 55 'comment_f_path': None,
56 56 'comment_id': resp_comment_id,
57 57 'comment_lineno': None,
58 58 'comment_status': {'status': 'under_review',
59 59 'status_lbl': 'Under Review'},
60 60 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
61 61 'comment_type': 'note',
62 62 'pull_request_version': None}
63 63 ]
64 64 assert_ok(id_, expected, response.body)
65 65
66 66 def test_api_get_pull_request_comments_repo_error(self, pr_util):
67 67 pull_request = pr_util.create_pull_request()
68 68 id_, params = build_data(
69 69 self.apikey, 'get_pull_request_comments',
70 70 repoid=666, pullrequestid=pull_request.pull_request_id)
71 71 response = api_call(self.app, params)
72 72
73 73 expected = 'repository `666` does not exist'
74 74 assert_error(id_, expected, given=response.body)
75 75
76 76 def test_api_get_pull_request_comments_pull_request_error(self):
77 77 id_, params = build_data(
78 78 self.apikey, 'get_pull_request_comments', pullrequestid=666)
79 79 response = api_call(self.app, params)
80 80
81 81 expected = 'pull request `666` does not exist'
82 82 assert_error(id_, expected, given=response.body)
@@ -1,80 +1,80 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.pull_request import PullRequestModel
26 26 from rhodecode.api.tests.utils import (
27 27 build_data, api_call, assert_error)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetPullRequest(object):
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 title=pull_request.title,
44 44 description=pull_request.description,
45 45 )
46 46 Session().commit()
47 47 id_, params = build_data(
48 48 self.apikey, 'get_pull_requests',
49 49 repoid=pull_request.target_repo.repo_name)
50 50 response = api_call(self.app, params)
51 51 assert response.status == '200 OK'
52 52 assert len(response.json['result']) == 2
53 53
54 54 PullRequestModel().close_pull_request(
55 55 pull_request_2, pull_request_2.author)
56 56 Session().commit()
57 57
58 58 id_, params = build_data(
59 59 self.apikey, 'get_pull_requests',
60 60 repoid=pull_request.target_repo.repo_name,
61 61 status='new')
62 62 response = api_call(self.app, params)
63 63 assert response.status == '200 OK'
64 64 assert len(response.json['result']) == 1
65 65
66 66 id_, params = build_data(
67 67 self.apikey, 'get_pull_requests',
68 68 repoid=pull_request.target_repo.repo_name,
69 69 status='closed')
70 70 response = api_call(self.app, params)
71 71 assert response.status == '200 OK'
72 72 assert len(response.json['result']) == 1
73 73
74 74 @pytest.mark.backends("git", "hg")
75 75 def test_api_get_pull_requests_repo_error(self):
76 76 id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666)
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)
@@ -1,137 +1,137 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
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, expected_permissions)
30 30
31 31
32 32 @pytest.mark.usefixtures("testuser_api", "app")
33 33 class TestGetRepo(object):
34 34 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
35 35 ('apikey', True),
36 36 ('apikey_regular', False),
37 37 ])
38 38 @pytest.mark.parametrize("cache_param", [
39 39 True,
40 40 False,
41 41 None,
42 42 ])
43 43 def test_api_get_repo(
44 44 self, apikey_attr, expect_secrets, cache_param, backend,
45 45 user_util):
46 46 repo = backend.create_repo()
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 for user in repo.followers:
68 68 followers.append(user.user.get_api_data(
69 69 include_secrets=expect_secrets))
70 70
71 71 ret['permissions'] = permissions
72 72 ret['followers'] = followers
73 73
74 74 expected = ret
75 75
76 76 assert_ok(id_, expected, given=response.body)
77 77
78 78 @pytest.mark.parametrize("grant_perm", [
79 79 'repository.admin',
80 80 'repository.write',
81 81 'repository.read',
82 82 ])
83 83 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
84 84 # TODO: Depending on which tests are running before this one, we
85 85 # start with a different number of permissions in the database.
86 86 repo = RepoModel().get_by_repo_name(backend.repo_name)
87 87 permission_count = len(repo.repo_to_perm)
88 88
89 89 RepoModel().grant_user_permission(repo=backend.repo_name,
90 90 user=self.TEST_USER_LOGIN,
91 91 perm=grant_perm)
92 92 Session().commit()
93 93 id_, params = build_data(
94 94 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
95 95 response = api_call(self.app, params)
96 96
97 97 repo = RepoModel().get_by_repo_name(backend.repo_name)
98 98 ret = repo.get_api_data()
99 99
100 100 assert permission_count + 1, len(repo.repo_to_perm)
101 101
102 102 permissions = expected_permissions(repo)
103 103
104 104 followers = []
105 105 for user in repo.followers:
106 106 followers.append(user.user.get_api_data())
107 107
108 108 ret['permissions'] = permissions
109 109 ret['followers'] = followers
110 110
111 111 expected = ret
112 112 try:
113 113 assert_ok(id_, expected, given=response.body)
114 114 finally:
115 115 RepoModel().revoke_user_permission(
116 116 backend.repo_name, self.TEST_USER_LOGIN)
117 117
118 118 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
119 119 RepoModel().grant_user_permission(repo=backend.repo_name,
120 120 user=self.TEST_USER_LOGIN,
121 121 perm='repository.none')
122 122
123 123 id_, params = build_data(
124 124 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
125 125 response = api_call(self.app, params)
126 126
127 127 expected = 'repository `%s` does not exist' % (backend.repo_name)
128 128 assert_error(id_, expected, given=response.body)
129 129
130 130 def test_api_get_repo_not_existing(self):
131 131 id_, params = build_data(
132 132 self.apikey, 'get_repo', repoid='no-such-repo')
133 133 response = api_call(self.app, params)
134 134
135 135 ret = 'repository `%s` does not exist' % 'no-such-repo'
136 136 expected = ret
137 137 assert_error(id_, expected, given=response.body)
@@ -1,141 +1,141 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_error
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestGetRepoChangeset(object):
29 29 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
30 30 def test_get_repo_changeset(self, details, backend):
31 31 commit = backend.repo.get_commit(commit_idx=0)
32 32 __, params = build_data(
33 33 self.apikey, 'get_repo_changeset',
34 34 repoid=backend.repo_name, revision=commit.raw_id,
35 35 details=details,
36 36 )
37 37 response = api_call(self.app, params)
38 38 result = response.json['result']
39 39 assert result['revision'] == 0
40 40 assert result['raw_id'] == commit.raw_id
41 41
42 42 if details == 'full':
43 43 assert result['refs']['bookmarks'] == getattr(
44 44 commit, 'bookmarks', [])
45 45 branches = [commit.branch] if commit.branch else []
46 46 assert result['refs']['branches'] == branches
47 47 assert result['refs']['tags'] == commit.tags
48 48
49 49 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
50 50 def test_get_repo_changeset_bad_type(self, details, backend):
51 51 id_, params = build_data(
52 52 self.apikey, 'get_repo_changeset',
53 53 repoid=backend.repo_name, revision=0,
54 54 details=details,
55 55 )
56 56 response = api_call(self.app, params)
57 57 expected = 'commit_id must be a string value'
58 58 assert_error(id_, expected, given=response.body)
59 59
60 60 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
61 61 def test_get_repo_changesets(self, details, backend):
62 62 limit = 2
63 63 commit = backend.repo.get_commit(commit_idx=0)
64 64 __, params = build_data(
65 65 self.apikey, 'get_repo_changesets',
66 66 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
67 67 details=details,
68 68 )
69 69 response = api_call(self.app, params)
70 70 result = response.json['result']
71 71 assert result
72 72 assert len(result) == limit
73 73 for x in xrange(limit):
74 74 assert result[x]['revision'] == x
75 75
76 76 if details == 'full':
77 77 for x in xrange(limit):
78 78 assert 'bookmarks' in result[x]['refs']
79 79 assert 'branches' in result[x]['refs']
80 80 assert 'tags' in result[x]['refs']
81 81
82 82 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
83 83 @pytest.mark.parametrize("start_rev, expected_revision", [
84 84 ("0", 0),
85 85 ("10", 10),
86 86 ("20", 20),
87 87 ])
88 88 @pytest.mark.backends("hg", "git")
89 89 def test_get_repo_changesets_commit_range(
90 90 self, details, backend, start_rev, expected_revision):
91 91 limit = 10
92 92 __, params = build_data(
93 93 self.apikey, 'get_repo_changesets',
94 94 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
95 95 details=details,
96 96 )
97 97 response = api_call(self.app, params)
98 98 result = response.json['result']
99 99 assert result
100 100 assert len(result) == limit
101 101 for i in xrange(limit):
102 102 assert result[i]['revision'] == int(expected_revision) + i
103 103
104 104 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
105 105 @pytest.mark.parametrize("start_rev, expected_revision", [
106 106 ("0", 0),
107 107 ("10", 9),
108 108 ("20", 19),
109 109 ])
110 110 def test_get_repo_changesets_commit_range_svn(
111 111 self, details, backend_svn, start_rev, expected_revision):
112 112
113 113 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
114 114 # in our API allows to pass in a "Commit ID" as well as a
115 115 # "Commit Index". In the case of Subversion it is not possible to
116 116 # distinguish these cases. As a workaround we implemented this
117 117 # behavior which gives a preference to see it as a "Commit ID".
118 118
119 119 limit = 10
120 120 __, params = build_data(
121 121 self.apikey, 'get_repo_changesets',
122 122 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
123 123 details=details,
124 124 )
125 125 response = api_call(self.app, params)
126 126 result = response.json['result']
127 127 assert result
128 128 assert len(result) == limit
129 129 for i in xrange(limit):
130 130 assert result[i]['revision'] == int(expected_revision) + i
131 131
132 132 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
133 133 def test_get_repo_changesets_bad_type(self, details, backend):
134 134 id_, params = build_data(
135 135 self.apikey, 'get_repo_changesets',
136 136 repoid=backend.repo_name, start_rev=0, limit=2,
137 137 details=details,
138 138 )
139 139 response = api_call(self.app, params)
140 140 expected = 'commit_id must be a string value'
141 141 assert_error(id_, expected, given=response.body)
@@ -1,55 +1,55 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, expected_permissions)
27 27
28 28
29 29 @pytest.mark.usefixtures("testuser_api", "app")
30 30 class TestApiGetRepoGroup(object):
31 31 def test_api_get_repo_group(self, user_util):
32 32 repo_group = user_util.create_repo_group()
33 33 repo_group_name = repo_group.group_name
34 34
35 35 id_, params = build_data(
36 36 self.apikey, 'get_repo_group', repogroupid=repo_group_name)
37 37 response = api_call(self.app, params)
38 38
39 39 repo_group = RepoGroupModel()._get_repo_group(repo_group_name)
40 40 ret = repo_group.get_api_data()
41 41
42 42 permissions = expected_permissions(repo_group)
43 43
44 44 ret['permissions'] = permissions
45 45 expected = ret
46 46 assert_ok(id_, expected, given=response.body)
47 47
48 48 def test_api_get_repo_group_not_existing(self):
49 49 id_, params = build_data(
50 50 self.apikey, 'get_repo_group', repogroupid='no-such-repo-group')
51 51 response = api_call(self.app, params)
52 52
53 53 ret = 'repository group `%s` does not exist' % 'no-such-repo-group'
54 54 expected = ret
55 55 assert_error(id_, expected, given=response.body)
@@ -1,40 +1,40 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.model.repo_group import RepoGroupModel
25 25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestApiGetRepoGroups(object):
30 30 def test_api_get_repo_groups(self):
31 31 id_, params = build_data(self.apikey, 'get_repo_groups')
32 32 response = api_call(self.app, params)
33 33
34 34 result = []
35 35 for repo in RepoGroupModel().get_all():
36 36 result.append(repo.get_api_data())
37 37 ret = jsonify(result)
38 38
39 39 expected = ret
40 40 assert_ok(id_, expected, given=response.body)
@@ -1,142 +1,142 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.model.meta import Session
25 25 from rhodecode.model.repo import RepoModel
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 TestGetRepoNodes(object):
32 32 @pytest.mark.parametrize("name, ret_type", [
33 33 ('all', 'all'),
34 34 ('dirs', 'dirs'),
35 35 ('files', 'files'),
36 36 ])
37 37 def test_api_get_repo_nodes(self, name, ret_type, backend):
38 38 commit_id = 'tip'
39 39 path = '/'
40 40 id_, params = build_data(
41 41 self.apikey, 'get_repo_nodes',
42 42 repoid=backend.repo_name, revision=commit_id,
43 43 root_path=path,
44 44 ret_type=ret_type)
45 45 response = api_call(self.app, params)
46 46
47 47 # we don't the actual return types here since it's tested somewhere
48 48 # else
49 49 expected = response.json['result']
50 50 assert_ok(id_, expected, given=response.body)
51 51
52 52 def test_api_get_repo_nodes_bad_commits(self, backend):
53 53 commit_id = 'i-dont-exist'
54 54 path = '/'
55 55 id_, params = build_data(
56 56 self.apikey, 'get_repo_nodes',
57 57 repoid=backend.repo_name, revision=commit_id,
58 58 root_path=path, )
59 59 response = api_call(self.app, params)
60 60
61 61 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
62 62 assert_error(id_, expected, given=response.body)
63 63
64 64 def test_api_get_repo_nodes_bad_path(self, backend):
65 65 commit_id = 'tip'
66 66 path = '/idontexits'
67 67 id_, params = build_data(
68 68 self.apikey, 'get_repo_nodes',
69 69 repoid=backend.repo_name, revision=commit_id,
70 70 root_path=path, )
71 71 response = api_call(self.app, params)
72 72
73 73 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
74 74 assert_error(id_, expected, given=response.body)
75 75
76 76 def test_api_get_repo_nodes_max_file_bytes(self, backend):
77 77 commit_id = 'tip'
78 78 path = '/'
79 79 max_file_bytes = 500
80 80
81 81 id_, params = build_data(
82 82 self.apikey, 'get_repo_nodes',
83 83 repoid=backend.repo_name, revision=commit_id, details='full',
84 84 root_path=path)
85 85 response = api_call(self.app, params)
86 86 assert any(file['content'] and len(file['content']) > max_file_bytes
87 87 for file in response.json['result'])
88 88
89 89 id_, params = build_data(
90 90 self.apikey, 'get_repo_nodes',
91 91 repoid=backend.repo_name, revision=commit_id,
92 92 root_path=path, details='full',
93 93 max_file_bytes=max_file_bytes)
94 94 response = api_call(self.app, params)
95 95 assert all(
96 96 file['content'] is None if file['size'] > max_file_bytes else True
97 97 for file in response.json['result'])
98 98
99 99 def test_api_get_repo_nodes_bad_ret_type(self, backend):
100 100 commit_id = 'tip'
101 101 path = '/'
102 102 ret_type = 'error'
103 103 id_, params = build_data(
104 104 self.apikey, 'get_repo_nodes',
105 105 repoid=backend.repo_name, revision=commit_id,
106 106 root_path=path,
107 107 ret_type=ret_type)
108 108 response = api_call(self.app, params)
109 109
110 110 expected = ('ret_type must be one of %s'
111 111 % (','.join(['all', 'dirs', 'files'])))
112 112 assert_error(id_, expected, given=response.body)
113 113
114 114 @pytest.mark.parametrize("name, ret_type, grant_perm", [
115 115 ('all', 'all', 'repository.write'),
116 116 ('dirs', 'dirs', 'repository.admin'),
117 117 ('files', 'files', 'repository.read'),
118 118 ])
119 119 def test_api_get_repo_nodes_by_regular_user(
120 120 self, name, ret_type, grant_perm, backend):
121 121 RepoModel().grant_user_permission(repo=backend.repo_name,
122 122 user=self.TEST_USER_LOGIN,
123 123 perm=grant_perm)
124 124 Session().commit()
125 125
126 126 commit_id = 'tip'
127 127 path = '/'
128 128 id_, params = build_data(
129 129 self.apikey_regular, 'get_repo_nodes',
130 130 repoid=backend.repo_name, revision=commit_id,
131 131 root_path=path,
132 132 ret_type=ret_type)
133 133 response = api_call(self.app, params)
134 134
135 135 # we don't the actual return types here since it's tested somewhere
136 136 # else
137 137 expected = response.json['result']
138 138 try:
139 139 assert_ok(id_, expected, given=response.body)
140 140 finally:
141 141 RepoModel().revoke_user_permission(
142 142 backend.repo_name, self.TEST_USER_LOGIN)
@@ -1,40 +1,40 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
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, expected_permissions)
30 30
31 31
32 32 @pytest.mark.usefixtures("testuser_api", "app")
33 33 class TestGetRepo(object):
34 34 def test_api_get_repo_refs(self, backend, user_util):
35 35 repo = backend.create_repo()
36 36 id_, params = build_data(self.apikey, 'get_repo_refs',
37 37 **{'repoid': repo.repo_name,})
38 38 response = api_call(self.app, params)
39 39 expected = repo.scm_instance().refs()
40 40 assert_ok(id_, expected, given=response.body)
@@ -1,129 +1,129 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.model.repo import RepoModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, jsonify)
27 27 from rhodecode.model.db import User
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetRepos(object):
32 32 def test_api_get_repos(self):
33 33 id_, params = build_data(self.apikey, 'get_repos')
34 34 response = api_call(self.app, params)
35 35
36 36 result = []
37 37 for repo in RepoModel().get_all():
38 38 result.append(repo.get_api_data(include_secrets=True))
39 39 ret = jsonify(result)
40 40
41 41 expected = ret
42 42 assert_ok(id_, expected, given=response.body)
43 43
44 44 def test_api_get_repos_only_toplevel(self, user_util):
45 45 repo_group = user_util.create_repo_group(auto_cleanup=True)
46 46 user_util.create_repo(parent=repo_group)
47 47
48 48 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
49 49 response = api_call(self.app, params)
50 50
51 51 result = []
52 52 for repo in RepoModel().get_repos_for_root(root=None):
53 53 result.append(repo.get_api_data(include_secrets=True))
54 54 expected = jsonify(result)
55 55
56 56 assert_ok(id_, expected, given=response.body)
57 57
58 58 def test_api_get_repos_with_wrong_root(self):
59 59 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
60 60 response = api_call(self.app, params)
61 61
62 62 expected = 'Root repository group `abracadabra` does not exist'
63 63 assert_error(id_, expected, given=response.body)
64 64
65 65 def test_api_get_repos_with_root(self, user_util):
66 66 repo_group = user_util.create_repo_group(auto_cleanup=True)
67 67 repo_group_name = repo_group.group_name
68 68
69 69 user_util.create_repo(parent=repo_group)
70 70 user_util.create_repo(parent=repo_group)
71 71
72 72 # nested, should not show up
73 73 user_util._test_name = '{}/'.format(repo_group_name)
74 74 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
75 75 user_util.create_repo(parent=sub_repo_group)
76 76
77 77 id_, params = build_data(self.apikey, 'get_repos',
78 78 root=repo_group_name, traverse=0)
79 79 response = api_call(self.app, params)
80 80
81 81 result = []
82 82 for repo in RepoModel().get_repos_for_root(repo_group):
83 83 result.append(repo.get_api_data(include_secrets=True))
84 84
85 85 assert len(result) == 2
86 86 expected = jsonify(result)
87 87 assert_ok(id_, expected, given=response.body)
88 88
89 89 def test_api_get_repos_with_root_and_traverse(self, user_util):
90 90 repo_group = user_util.create_repo_group(auto_cleanup=True)
91 91 repo_group_name = repo_group.group_name
92 92
93 93 user_util.create_repo(parent=repo_group)
94 94 user_util.create_repo(parent=repo_group)
95 95
96 96 # nested, should not show up
97 97 user_util._test_name = '{}/'.format(repo_group_name)
98 98 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
99 99 user_util.create_repo(parent=sub_repo_group)
100 100
101 101 id_, params = build_data(self.apikey, 'get_repos',
102 102 root=repo_group_name, traverse=1)
103 103 response = api_call(self.app, params)
104 104
105 105 result = []
106 106 for repo in RepoModel().get_repos_for_root(
107 107 repo_group_name, traverse=True):
108 108 result.append(repo.get_api_data(include_secrets=True))
109 109
110 110 assert len(result) == 3
111 111 expected = jsonify(result)
112 112 assert_ok(id_, expected, given=response.body)
113 113
114 114 def test_api_get_repos_non_admin(self):
115 115 id_, params = build_data(self.apikey_regular, 'get_repos')
116 116 response = api_call(self.app, params)
117 117
118 118 user = User.get_by_username(self.TEST_USER_LOGIN)
119 119 allowed_repos = user.AuthUser().permissions['repositories']
120 120
121 121 result = []
122 122 for repo in RepoModel().get_all():
123 123 perm = allowed_repos[repo.repo_name]
124 124 if perm in ['repository.read', 'repository.write', 'repository.admin']:
125 125 result.append(repo.get_api_data())
126 126 ret = jsonify(result)
127 127
128 128 expected = ret
129 129 assert_ok(id_, expected, given=response.body)
@@ -1,84 +1,84 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.model.scm import ScmModel
25 25 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
26 26
27 27
28 28 @pytest.fixture
29 29 def http_host_stub():
30 30 """
31 31 To ensure that we can get an IP address, this test shall run with a
32 32 hostname set to "localhost".
33 33 """
34 34 return 'localhost:80'
35 35
36 36
37 37 @pytest.mark.usefixtures("testuser_api", "app")
38 38 class TestGetServerInfo(object):
39 39 def test_api_get_server_info(self):
40 40 id_, params = build_data(self.apikey, 'get_server_info')
41 41 response = api_call(self.app, params)
42 42 resp = response.json
43 43 expected = ScmModel().get_server_info()
44 44 expected['memory'] = resp['result']['memory']
45 45 expected['uptime'] = resp['result']['uptime']
46 46 expected['load'] = resp['result']['load']
47 47 expected['cpu'] = resp['result']['cpu']
48 48 expected['storage'] = resp['result']['storage']
49 49 expected['storage_temp'] = resp['result']['storage_temp']
50 50 expected['storage_inodes'] = resp['result']['storage_inodes']
51 51 expected['server'] = resp['result']['server']
52 52
53 53 expected['index_storage'] = resp['result']['index_storage']
54 54 expected['storage'] = resp['result']['storage']
55 55
56 56 assert_ok(id_, expected, given=response.body)
57 57
58 58 def test_api_get_server_info_ip(self):
59 59 id_, params = build_data(self.apikey, 'get_server_info')
60 60 response = api_call(self.app, params)
61 61 resp = response.json
62 62 expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'})
63 63 expected['memory'] = resp['result']['memory']
64 64 expected['uptime'] = resp['result']['uptime']
65 65 expected['load'] = resp['result']['load']
66 66 expected['cpu'] = resp['result']['cpu']
67 67 expected['storage'] = resp['result']['storage']
68 68 expected['storage_temp'] = resp['result']['storage_temp']
69 69 expected['storage_inodes'] = resp['result']['storage_inodes']
70 70 expected['server'] = resp['result']['server']
71 71
72 72 expected['index_storage'] = resp['result']['index_storage']
73 73 expected['storage'] = resp['result']['storage']
74 74
75 75 assert_ok(id_, expected, given=response.body)
76 76
77 77 def test_api_get_server_info_data_for_search_index_build(self):
78 78 id_, params = build_data(self.apikey, 'get_server_info')
79 79 response = api_call(self.app, params)
80 80 resp = response.json
81 81
82 82 # required by indexer
83 83 assert resp['result']['index_storage']
84 84 assert resp['result']['storage']
@@ -1,86 +1,86 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.lib.auth import AuthUser
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)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetUser(object):
32 32 def test_api_get_user(self):
33 33 id_, params = build_data(
34 34 self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN)
35 35 response = api_call(self.app, params)
36 36
37 37 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
38 38 ret = usr.get_api_data(include_secrets=True)
39 39 permissions = AuthUser(usr.user_id).permissions
40 40 ret['permissions'] = permissions
41 41 ret['permissions_summary'] = permissions
42 42
43 43 expected = ret
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 def test_api_get_user_not_existing(self):
47 47 id_, params = build_data(self.apikey, 'get_user', userid='trololo')
48 48 response = api_call(self.app, params)
49 49
50 50 expected = "user `%s` does not exist" % 'trololo'
51 51 assert_error(id_, expected, given=response.body)
52 52
53 53 def test_api_get_user_without_giving_userid(self):
54 54 id_, params = build_data(self.apikey, 'get_user')
55 55 response = api_call(self.app, params)
56 56
57 57 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
58 58 ret = usr.get_api_data(include_secrets=True)
59 59 permissions = AuthUser(usr.user_id).permissions
60 60 ret['permissions'] = permissions
61 61 ret['permissions_summary'] = permissions
62 62
63 63 expected = ret
64 64 assert_ok(id_, expected, given=response.body)
65 65
66 66 def test_api_get_user_without_giving_userid_non_admin(self):
67 67 id_, params = build_data(self.apikey_regular, 'get_user')
68 68 response = api_call(self.app, params)
69 69
70 70 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
71 71 ret = usr.get_api_data(include_secrets=True)
72 72 permissions = AuthUser(usr.user_id).permissions
73 73 ret['permissions'] = permissions
74 74 ret['permissions_summary'] = permissions
75 75
76 76 expected = ret
77 77 assert_ok(id_, expected, given=response.body)
78 78
79 79 def test_api_get_user_with_giving_userid_non_admin(self):
80 80 id_, params = build_data(
81 81 self.apikey_regular, 'get_user',
82 82 userid=self.TEST_USER_LOGIN)
83 83 response = api_call(self.app, params)
84 84
85 85 expected = 'userid is not the same as your user'
86 86 assert_error(id_, expected, given=response.body)
@@ -1,76 +1,76 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.user import UserModel
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 TestGetUserGroups(object):
30 30 def test_api_get_user_group(self, user_util):
31 31 user, group = user_util.create_user_with_group()
32 32 id_, params = build_data(
33 33 self.apikey, 'get_user_group', usergroupid=group.users_group_name)
34 34 response = api_call(self.app, params)
35 35
36 36 ret = group.get_api_data()
37 37 ret['users'] = [user.get_api_data()]
38 38
39 39 permissions = expected_permissions(group)
40 40
41 41 ret['permissions'] = permissions
42 42 ret['permissions_summary'] = response.json['result']['permissions_summary']
43 43 expected = ret
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 def test_api_get_user_group_regular_user(self, user_util):
47 47 user, group = user_util.create_user_with_group()
48 48 id_, params = build_data(
49 49 self.apikey_regular, 'get_user_group',
50 50 usergroupid=group.users_group_name)
51 51 response = api_call(self.app, params)
52 52
53 53 ret = group.get_api_data()
54 54 ret['users'] = [user.get_api_data()]
55 55
56 56 permissions = expected_permissions(group)
57 57
58 58 ret['permissions'] = permissions
59 59 ret['permissions_summary'] = response.json['result']['permissions_summary']
60 60 expected = ret
61 61 assert_ok(id_, expected, given=response.body)
62 62
63 63 def test_api_get_user_group_regular_user_permission_denied(
64 64 self, user_util):
65 65 group = user_util.create_user_group()
66 66 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
67 67 group_name = group.users_group_name
68 68 user_util.grant_user_permission_to_user_group(
69 69 group, user, 'usergroup.none')
70 70
71 71 id_, params = build_data(
72 72 self.apikey_regular, 'get_user_group', usergroupid=group_name)
73 73 response = api_call(self.app, params)
74 74
75 75 expected = 'user group `%s` does not exist' % (group_name,)
76 76 assert_error(id_, expected, given=response.body)
@@ -1,71 +1,71 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 json
23 23
24 24 import pytest
25 25
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.api.tests.utils import build_data, api_call
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestGetUserGroups(object):
32 32 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
33 33 ('apikey', True),
34 34 ('apikey_regular', False),
35 35 ])
36 36 def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util):
37 37 first_group = user_util.create_user_group()
38 38 second_group = user_util.create_user_group()
39 39 expected = [
40 40 g.get_api_data(include_secrets=expect_secrets)
41 41 for g in (first_group, second_group)]
42 42
43 43 apikey = getattr(self, apikey_attr)
44 44 id_, params = build_data(apikey, 'get_user_groups', )
45 45 response = api_call(self.app, params)
46 46 self._assert_ok(id_, expected, response)
47 47
48 48 def test_api_get_user_groups_regular_user(self, user_util):
49 49 first_group = user_util.create_user_group()
50 50 second_group = user_util.create_user_group()
51 51 expected = [g.get_api_data() for g in (first_group, second_group)]
52 52
53 53 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
54 54 response = api_call(self.app, params)
55 55 self._assert_ok(id_, expected, response)
56 56
57 57 def test_api_get_user_groups_regular_user_no_permission(self, user_util):
58 58 group = user_util.create_user_group()
59 59 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
60 60 user_util.grant_user_permission_to_user_group(
61 61 group, user, 'usergroup.none')
62 62 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
63 63 response = api_call(self.app, params)
64 64 expected = []
65 65 self._assert_ok(id_, expected, response)
66 66
67 67 def _assert_ok(self, id_, expected_list, response):
68 68 result = json.loads(response.body)
69 69 assert result['id'] == id_
70 70 assert result['error'] is None
71 71 assert sorted(result['result']) == sorted(expected_list)
@@ -1,40 +1,40 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import User
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, jsonify)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestGetUsers(object):
30 30 def test_api_get_users(self):
31 31 id_, params = build_data(self.apikey, 'get_users', )
32 32 response = api_call(self.app, params)
33 33 ret_all = []
34 34 _users = User.query().filter(User.username != User.DEFAULT_USER) \
35 35 .order_by(User.username).all()
36 36 for usr in _users:
37 37 ret = usr.get_api_data(include_secrets=True)
38 38 ret_all.append(jsonify(ret))
39 39 expected = ret_all
40 40 assert_ok(id_, expected, given=response.body)
@@ -1,90 +1,90 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.repo import RepoModel
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 TestGrantUserGroupPermission(object):
31 31 @pytest.mark.parametrize("name, perm", [
32 32 ('none', 'repository.none'),
33 33 ('read', 'repository.read'),
34 34 ('write', 'repository.write'),
35 35 ('admin', 'repository.admin')
36 36 ])
37 37 def test_api_grant_user_group_permission(
38 38 self, name, perm, backend, user_util):
39 39 user_group = user_util.create_user_group()
40 40 id_, params = build_data(
41 41 self.apikey,
42 42 'grant_user_group_permission',
43 43 repoid=backend.repo_name,
44 44 usergroupid=user_group.users_group_name,
45 45 perm=perm)
46 46 response = api_call(self.app, params)
47 47
48 48 ret = {
49 49 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
50 50 perm, user_group.users_group_name, backend.repo_name
51 51 ),
52 52 'success': True
53 53 }
54 54 expected = ret
55 55 assert_ok(id_, expected, given=response.body)
56 56
57 57 def test_api_grant_user_group_permission_wrong_permission(
58 58 self, backend, user_util):
59 59 perm = 'haha.no.permission'
60 60 user_group = user_util.create_user_group()
61 61 id_, params = build_data(
62 62 self.apikey,
63 63 'grant_user_group_permission',
64 64 repoid=backend.repo_name,
65 65 usergroupid=user_group.users_group_name,
66 66 perm=perm)
67 67 response = api_call(self.app, params)
68 68
69 69 expected = 'permission `%s` does not exist' % (perm,)
70 70 assert_error(id_, expected, given=response.body)
71 71
72 72 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
73 73 def test_api_grant_user_group_permission_exception_when_adding(
74 74 self, backend, user_util):
75 75 perm = 'repository.read'
76 76 user_group = user_util.create_user_group()
77 77 id_, params = build_data(
78 78 self.apikey,
79 79 'grant_user_group_permission',
80 80 repoid=backend.repo_name,
81 81 usergroupid=user_group.users_group_name,
82 82 perm=perm)
83 83 response = api_call(self.app, params)
84 84
85 85 expected = (
86 86 'failed to edit permission for user group: `%s` in repo: `%s`' % (
87 87 user_group.users_group_name, backend.repo_name
88 88 )
89 89 )
90 90 assert_error(id_, expected, given=response.body)
@@ -1,174 +1,174 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.user import UserModel
25 25 from rhodecode.model.repo_group import RepoGroupModel
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 TestGrantUserGroupPermissionFromRepoGroup(object):
32 32 @pytest.mark.parametrize("name, perm, apply_to_children", [
33 33 ('none', 'group.none', 'none'),
34 34 ('read', 'group.read', 'none'),
35 35 ('write', 'group.write', 'none'),
36 36 ('admin', 'group.admin', 'none'),
37 37
38 38 ('none', 'group.none', 'all'),
39 39 ('read', 'group.read', 'all'),
40 40 ('write', 'group.write', 'all'),
41 41 ('admin', 'group.admin', 'all'),
42 42
43 43 ('none', 'group.none', 'repos'),
44 44 ('read', 'group.read', 'repos'),
45 45 ('write', 'group.write', 'repos'),
46 46 ('admin', 'group.admin', 'repos'),
47 47
48 48 ('none', 'group.none', 'groups'),
49 49 ('read', 'group.read', 'groups'),
50 50 ('write', 'group.write', 'groups'),
51 51 ('admin', 'group.admin', 'groups'),
52 52 ])
53 53 def test_api_grant_user_group_permission_to_repo_group(
54 54 self, name, perm, apply_to_children, user_util):
55 55 user_group = user_util.create_user_group()
56 56 repo_group = user_util.create_repo_group()
57 57 user_util.create_repo(parent=repo_group)
58 58
59 59 id_, params = build_data(
60 60 self.apikey,
61 61 'grant_user_group_permission_to_repo_group',
62 62 repogroupid=repo_group.name,
63 63 usergroupid=user_group.users_group_name,
64 64 perm=perm,
65 65 apply_to_children=apply_to_children,)
66 66 response = api_call(self.app, params)
67 67
68 68 ret = {
69 69 'msg': (
70 70 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
71 71 ' in repo group: `%s`' % (
72 72 perm, apply_to_children, user_group.users_group_name,
73 73 repo_group.name
74 74 )
75 75 ),
76 76 'success': True
77 77 }
78 78 expected = ret
79 79 try:
80 80 assert_ok(id_, expected, given=response.body)
81 81 finally:
82 82 RepoGroupModel().revoke_user_group_permission(
83 83 repo_group.group_id, user_group.users_group_id)
84 84
85 85 @pytest.mark.parametrize(
86 86 "name, perm, apply_to_children, grant_admin, access_ok", [
87 87 ('none_fails', 'group.none', 'none', False, False),
88 88 ('read_fails', 'group.read', 'none', False, False),
89 89 ('write_fails', 'group.write', 'none', False, False),
90 90 ('admin_fails', 'group.admin', 'none', False, False),
91 91
92 92 # with granted perms
93 93 ('none_ok', 'group.none', 'none', True, True),
94 94 ('read_ok', 'group.read', 'none', True, True),
95 95 ('write_ok', 'group.write', 'none', True, True),
96 96 ('admin_ok', 'group.admin', 'none', True, True),
97 97 ]
98 98 )
99 99 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
100 100 self, name, perm, apply_to_children, grant_admin, access_ok,
101 101 user_util):
102 102 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
103 103 user_group = user_util.create_user_group()
104 104 repo_group = user_util.create_repo_group()
105 105 if grant_admin:
106 106 user_util.grant_user_permission_to_repo_group(
107 107 repo_group, user, 'group.admin')
108 108
109 109 id_, params = build_data(
110 110 self.apikey_regular,
111 111 'grant_user_group_permission_to_repo_group',
112 112 repogroupid=repo_group.name,
113 113 usergroupid=user_group.users_group_name,
114 114 perm=perm,
115 115 apply_to_children=apply_to_children,)
116 116 response = api_call(self.app, params)
117 117 if access_ok:
118 118 ret = {
119 119 'msg': (
120 120 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
121 121 ' in repo group: `%s`' % (
122 122 perm, apply_to_children, user_group.users_group_name,
123 123 repo_group.name
124 124 )
125 125 ),
126 126 'success': True
127 127 }
128 128 expected = ret
129 129 try:
130 130 assert_ok(id_, expected, given=response.body)
131 131 finally:
132 132 RepoGroupModel().revoke_user_group_permission(
133 133 repo_group.group_id, user_group.users_group_id)
134 134 else:
135 135 expected = 'repository group `%s` does not exist' % (
136 136 repo_group.name,)
137 137 assert_error(id_, expected, given=response.body)
138 138
139 139 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
140 140 self, user_util):
141 141 user_group = user_util.create_user_group()
142 142 repo_group = user_util.create_repo_group()
143 143 perm = 'haha.no.permission'
144 144 id_, params = build_data(
145 145 self.apikey,
146 146 'grant_user_group_permission_to_repo_group',
147 147 repogroupid=repo_group.name,
148 148 usergroupid=user_group.users_group_name,
149 149 perm=perm)
150 150 response = api_call(self.app, params)
151 151
152 152 expected = 'permission `%s` does not exist' % (perm,)
153 153 assert_error(id_, expected, given=response.body)
154 154
155 155 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
156 156 def test_api_grant_user_group_permission_exception_when_adding_2(
157 157 self, user_util):
158 158 user_group = user_util.create_user_group()
159 159 repo_group = user_util.create_repo_group()
160 160 perm = 'group.read'
161 161 id_, params = build_data(
162 162 self.apikey,
163 163 'grant_user_group_permission_to_repo_group',
164 164 repogroupid=repo_group.name,
165 165 usergroupid=user_group.users_group_name,
166 166 perm=perm)
167 167 response = api_call(self.app, params)
168 168
169 169 expected = (
170 170 'failed to edit permission for user group: `%s`'
171 171 ' in repo group: `%s`' % (
172 172 user_group.users_group_name, repo_group.name)
173 173 )
174 174 assert_error(id_, expected, given=response.body)
@@ -1,97 +1,97 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.user_group import UserGroupModel
24 24 from rhodecode.api.tests.utils import (
25 25 build_data, api_call, assert_ok, assert_error)
26 26
27 27
28 28 @pytest.mark.usefixtures("testuser_api", "app")
29 29 class TestGrantUserGroupPermissionFromUserGroup(object):
30 30 @pytest.mark.parametrize("name, perm", [
31 31 ('none', 'usergroup.none'),
32 32 ('read', 'usergroup.read'),
33 33 ('write', 'usergroup.write'),
34 34 ('admin', 'usergroup.admin'),
35 35
36 36 ('none', 'usergroup.none'),
37 37 ('read', 'usergroup.read'),
38 38 ('write', 'usergroup.write'),
39 39 ('admin', 'usergroup.admin'),
40 40
41 41 ('none', 'usergroup.none'),
42 42 ('read', 'usergroup.read'),
43 43 ('write', 'usergroup.write'),
44 44 ('admin', 'usergroup.admin'),
45 45
46 46 ('none', 'usergroup.none'),
47 47 ('read', 'usergroup.read'),
48 48 ('write', 'usergroup.write'),
49 49 ('admin', 'usergroup.admin'),
50 50 ])
51 51 def test_api_grant_user_group_permission_to_user_group(
52 52 self, name, perm, user_util):
53 53 group = user_util.create_user_group()
54 54 target_group = user_util.create_user_group()
55 55
56 56 id_, params = build_data(
57 57 self.apikey,
58 58 'grant_user_group_permission_to_user_group',
59 59 usergroupid=target_group.users_group_name,
60 60 sourceusergroupid=group.users_group_name,
61 61 perm=perm)
62 62 response = api_call(self.app, params)
63 63
64 64 expected = {
65 65 'msg': (
66 66 'Granted perm: `%s` for user group: `%s`'
67 67 ' in user group: `%s`' % (
68 68 perm, group.users_group_name,
69 69 target_group.users_group_name
70 70 )
71 71 ),
72 72 'success': True
73 73 }
74 74 try:
75 75 assert_ok(id_, expected, given=response.body)
76 76 finally:
77 77 UserGroupModel().revoke_user_group_permission(
78 78 target_group.users_group_id, group.users_group_id)
79 79
80 80 def test_api_grant_user_group_permission_to_user_group_same_failure(
81 81 self, user_util):
82 82 group = user_util.create_user_group()
83 83
84 84 id_, params = build_data(
85 85 self.apikey,
86 86 'grant_user_group_permission_to_user_group',
87 87 usergroupid=group.users_group_name,
88 88 sourceusergroupid=group.users_group_name,
89 89 perm='usergroup.none')
90 90 response = api_call(self.app, params)
91 91
92 92 expected = (
93 93 'failed to edit permission for user group: `%s`'
94 94 ' in user group: `%s`' % (
95 95 group.users_group_name, group.users_group_name)
96 96 )
97 97 assert_error(id_, expected, given=response.body)
@@ -1,87 +1,87 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.repo import RepoModel
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 TestGrantUserPermission(object):
31 31 @pytest.mark.parametrize("name, perm", [
32 32 ('none', 'repository.none'),
33 33 ('read', 'repository.read'),
34 34 ('write', 'repository.write'),
35 35 ('admin', 'repository.admin')
36 36 ])
37 37 def test_api_grant_user_permission(self, name, perm, backend, user_util):
38 38 user = user_util.create_user()
39 39 id_, params = build_data(
40 40 self.apikey,
41 41 'grant_user_permission',
42 42 repoid=backend.repo_name,
43 43 userid=user.username,
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: `%s` in repo: `%s`' % (
49 49 perm, user.username, 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_permission_wrong_permission(
57 57 self, backend, user_util):
58 58 user = user_util.create_user()
59 59 perm = 'haha.no.permission'
60 60 id_, params = build_data(
61 61 self.apikey,
62 62 'grant_user_permission',
63 63 repoid=backend.repo_name,
64 64 userid=user.username,
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_permission', crash)
72 72 def test_api_grant_user_permission_exception_when_adding(
73 73 self, backend, user_util):
74 74 user = user_util.create_user()
75 75 perm = 'repository.read'
76 76 id_, params = build_data(
77 77 self.apikey,
78 78 'grant_user_permission',
79 79 repoid=backend.repo_name,
80 80 userid=user.username,
81 81 perm=perm)
82 82 response = api_call(self.app, params)
83 83
84 84 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
85 85 user.username, backend.repo_name
86 86 )
87 87 assert_error(id_, expected, given=response.body)
@@ -1,157 +1,157 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.user import UserModel
25 25 from rhodecode.model.repo_group import RepoGroupModel
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 TestGrantUserPermissionFromRepoGroup(object):
32 32 @pytest.mark.parametrize("name, perm, apply_to_children", [
33 33 ('none', 'group.none', 'none'),
34 34 ('read', 'group.read', 'none'),
35 35 ('write', 'group.write', 'none'),
36 36 ('admin', 'group.admin', 'none'),
37 37
38 38 ('none', 'group.none', 'all'),
39 39 ('read', 'group.read', 'all'),
40 40 ('write', 'group.write', 'all'),
41 41 ('admin', 'group.admin', 'all'),
42 42
43 43 ('none', 'group.none', 'repos'),
44 44 ('read', 'group.read', 'repos'),
45 45 ('write', 'group.write', 'repos'),
46 46 ('admin', 'group.admin', 'repos'),
47 47
48 48 ('none', 'group.none', 'groups'),
49 49 ('read', 'group.read', 'groups'),
50 50 ('write', 'group.write', 'groups'),
51 51 ('admin', 'group.admin', 'groups'),
52 52 ])
53 53 def test_api_grant_user_permission_to_repo_group(
54 54 self, name, perm, apply_to_children, user_util):
55 55 user = user_util.create_user()
56 56 repo_group = user_util.create_repo_group()
57 57 id_, params = build_data(
58 58 self.apikey, 'grant_user_permission_to_repo_group',
59 59 repogroupid=repo_group.name, userid=user.username,
60 60 perm=perm, apply_to_children=apply_to_children)
61 61 response = api_call(self.app, params)
62 62
63 63 ret = {
64 64 'msg': (
65 65 'Granted perm: `%s` (recursive:%s) for user: `%s`'
66 66 ' in repo group: `%s`' % (
67 67 perm, apply_to_children, user.username, repo_group.name
68 68 )
69 69 ),
70 70 'success': True
71 71 }
72 72 expected = ret
73 73 assert_ok(id_, expected, given=response.body)
74 74
75 75 @pytest.mark.parametrize(
76 76 "name, perm, apply_to_children, grant_admin, access_ok", [
77 77 ('none_fails', 'group.none', 'none', False, False),
78 78 ('read_fails', 'group.read', 'none', False, False),
79 79 ('write_fails', 'group.write', 'none', False, False),
80 80 ('admin_fails', 'group.admin', 'none', False, False),
81 81
82 82 # with granted perms
83 83 ('none_ok', 'group.none', 'none', True, True),
84 84 ('read_ok', 'group.read', 'none', True, True),
85 85 ('write_ok', 'group.write', 'none', True, True),
86 86 ('admin_ok', 'group.admin', 'none', True, True),
87 87 ]
88 88 )
89 89 def test_api_grant_user_permission_to_repo_group_by_regular_user(
90 90 self, name, perm, apply_to_children, grant_admin, access_ok,
91 91 user_util):
92 92 user = user_util.create_user()
93 93 repo_group = user_util.create_repo_group()
94 94
95 95 if grant_admin:
96 96 test_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
97 97 user_util.grant_user_permission_to_repo_group(
98 98 repo_group, test_user, 'group.admin')
99 99
100 100 id_, params = build_data(
101 101 self.apikey_regular, 'grant_user_permission_to_repo_group',
102 102 repogroupid=repo_group.name, userid=user.username,
103 103 perm=perm, apply_to_children=apply_to_children)
104 104 response = api_call(self.app, params)
105 105 if access_ok:
106 106 ret = {
107 107 'msg': (
108 108 'Granted perm: `%s` (recursive:%s) for user: `%s`'
109 109 ' in repo group: `%s`' % (
110 110 perm, apply_to_children, user.username, repo_group.name
111 111 )
112 112 ),
113 113 'success': True
114 114 }
115 115 expected = ret
116 116 assert_ok(id_, expected, given=response.body)
117 117 else:
118 118 expected = 'repository group `%s` does not exist' % (
119 119 repo_group.name, )
120 120 assert_error(id_, expected, given=response.body)
121 121
122 122 def test_api_grant_user_permission_to_repo_group_wrong_permission(
123 123 self, user_util):
124 124 user = user_util.create_user()
125 125 repo_group = user_util.create_repo_group()
126 126 perm = 'haha.no.permission'
127 127 id_, params = build_data(
128 128 self.apikey,
129 129 'grant_user_permission_to_repo_group',
130 130 repogroupid=repo_group.name,
131 131 userid=user.username,
132 132 perm=perm)
133 133 response = api_call(self.app, params)
134 134
135 135 expected = 'permission `%s` does not exist' % (perm,)
136 136 assert_error(id_, expected, given=response.body)
137 137
138 138 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
139 139 def test_api_grant_user_permission_to_repo_group_exception_when_adding(
140 140 self, user_util):
141 141 user = user_util.create_user()
142 142 repo_group = user_util.create_repo_group()
143 143 perm = 'group.read'
144 144 id_, params = build_data(
145 145 self.apikey,
146 146 'grant_user_permission_to_repo_group',
147 147 repogroupid=repo_group.name,
148 148 userid=user.username,
149 149 perm=perm)
150 150 response = api_call(self.app, params)
151 151
152 152 expected = (
153 153 'failed to edit permission for user: `%s` in repo group: `%s`' % (
154 154 user.username, repo_group.name
155 155 )
156 156 )
157 157 assert_error(id_, expected, given=response.body)
@@ -1,156 +1,156 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.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 TestGrantUserPermissionFromUserGroup(object):
32 32 @pytest.mark.parametrize("name, perm", [
33 33 ('none', 'usergroup.none'),
34 34 ('read', 'usergroup.read'),
35 35 ('write', 'usergroup.write'),
36 36 ('admin', 'usergroup.admin'),
37 37
38 38 ('none', 'usergroup.none'),
39 39 ('read', 'usergroup.read'),
40 40 ('write', 'usergroup.write'),
41 41 ('admin', 'usergroup.admin'),
42 42
43 43 ('none', 'usergroup.none'),
44 44 ('read', 'usergroup.read'),
45 45 ('write', 'usergroup.write'),
46 46 ('admin', 'usergroup.admin'),
47 47
48 48 ('none', 'usergroup.none'),
49 49 ('read', 'usergroup.read'),
50 50 ('write', 'usergroup.write'),
51 51 ('admin', 'usergroup.admin'),
52 52 ])
53 53 def test_api_grant_user_permission_to_user_group(
54 54 self, name, perm, user_util):
55 55 user = user_util.create_user()
56 56 group = user_util.create_user_group()
57 57 id_, params = build_data(
58 58 self.apikey,
59 59 'grant_user_permission_to_user_group',
60 60 usergroupid=group.users_group_name,
61 61 userid=user.username,
62 62 perm=perm)
63 63 response = api_call(self.app, params)
64 64
65 65 ret = {
66 66 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
67 67 perm, user.username, group.users_group_name
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("name, perm, grant_admin, access_ok", [
75 75 ('none_fails', 'usergroup.none', False, False),
76 76 ('read_fails', 'usergroup.read', False, False),
77 77 ('write_fails', 'usergroup.write', False, False),
78 78 ('admin_fails', 'usergroup.admin', False, False),
79 79
80 80 # with granted perms
81 81 ('none_ok', 'usergroup.none', True, True),
82 82 ('read_ok', 'usergroup.read', True, True),
83 83 ('write_ok', 'usergroup.write', True, True),
84 84 ('admin_ok', 'usergroup.admin', True, True),
85 85 ])
86 86 def test_api_grant_user_permission_to_user_group_by_regular_user(
87 87 self, name, perm, grant_admin, access_ok, user_util):
88 88 api_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
89 89 user = user_util.create_user()
90 90 group = user_util.create_user_group()
91 91 # grant the user ability to at least read the group
92 92 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
93 93 user_util.grant_user_permission_to_user_group(
94 94 group, api_user, permission)
95 95
96 96 id_, params = build_data(
97 97 self.apikey_regular,
98 98 'grant_user_permission_to_user_group',
99 99 usergroupid=group.users_group_name,
100 100 userid=user.username,
101 101 perm=perm)
102 102 response = api_call(self.app, params)
103 103
104 104 if access_ok:
105 105 ret = {
106 106 'msg': (
107 107 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
108 108 perm, user.username, group.users_group_name
109 109 )
110 110 ),
111 111 'success': True
112 112 }
113 113 expected = ret
114 114 assert_ok(id_, expected, given=response.body)
115 115 else:
116 116 expected = 'user group `%s` does not exist' % (
117 117 group.users_group_name)
118 118 assert_error(id_, expected, given=response.body)
119 119
120 120 def test_api_grant_user_permission_to_user_group_wrong_permission(
121 121 self, user_util):
122 122 user = user_util.create_user()
123 123 group = user_util.create_user_group()
124 124 perm = 'haha.no.permission'
125 125 id_, params = build_data(
126 126 self.apikey,
127 127 'grant_user_permission_to_user_group',
128 128 usergroupid=group.users_group_name,
129 129 userid=user.username,
130 130 perm=perm)
131 131 response = api_call(self.app, params)
132 132
133 133 expected = 'permission `%s` does not exist' % perm
134 134 assert_error(id_, expected, given=response.body)
135 135
136 136 def test_api_grant_user_permission_to_user_group_exception_when_adding(
137 137 self, user_util):
138 138 user = user_util.create_user()
139 139 group = user_util.create_user_group()
140 140
141 141 perm = 'usergroup.read'
142 142 id_, params = build_data(
143 143 self.apikey,
144 144 'grant_user_permission_to_user_group',
145 145 usergroupid=group.users_group_name,
146 146 userid=user.username,
147 147 perm=perm)
148 148 with mock.patch.object(UserGroupModel, 'grant_user_permission', crash):
149 149 response = api_call(self.app, params)
150 150
151 151 expected = (
152 152 'failed to edit permission for user: `%s` in user group: `%s`' % (
153 153 user.username, group.users_group_name
154 154 )
155 155 )
156 156 assert_error(id_, expected, given=response.body)
@@ -1,68 +1,68 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.scm import ScmModel
25 25 from rhodecode.api.tests.utils import (
26 26 build_data, api_call, assert_ok, assert_error, crash)
27 27 from rhodecode.model.repo import RepoModel
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestInvalidateCache(object):
32 32
33 33 def _set_cache(self, repo_name):
34 34 repo = RepoModel().get_by_repo_name(repo_name)
35 35 repo.scm_instance(cache=True)
36 36
37 37 def test_api_invalidate_cache(self, backend):
38 38 self._set_cache(backend.repo_name)
39 39
40 40 id_, params = build_data(
41 41 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
42 42 response = api_call(self.app, params)
43 43
44 44 expected = {
45 45 'msg': "Cache for repository `%s` was invalidated" % (
46 46 backend.repo_name,),
47 47 'repository': backend.repo_name,
48 48 }
49 49 assert_ok(id_, expected, given=response.body)
50 50
51 51 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
52 52 def test_api_invalidate_cache_error(self, backend):
53 53 id_, params = build_data(
54 54 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
55 55 response = api_call(self.app, params)
56 56
57 57 expected = 'Error occurred during cache invalidation action'
58 58 assert_error(id_, expected, given=response.body)
59 59
60 60 def test_api_invalidate_cache_regular_user_no_permission(self, backend):
61 61 self._set_cache(backend.repo_name)
62 62
63 63 id_, params = build_data(
64 64 self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name)
65 65 response = api_call(self.app, params)
66 66
67 67 expected = "repository `%s` does not exist" % (backend.repo_name,)
68 68 assert_error(id_, expected, given=response.body)
@@ -1,137 +1,137 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import UserLog, PullRequest
24 24 from rhodecode.model.meta import Session
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 TestMergePullRequest(object):
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 author = pull_request.user_id
36 36 repo = pull_request.target_repo.repo_id
37 37 pull_request_id = pull_request.pull_request_id
38 38 pull_request_repo = pull_request.target_repo.repo_name
39 39
40 40 id_, params = build_data(
41 41 self.apikey, 'merge_pull_request',
42 42 repoid=pull_request_repo,
43 43 pullrequestid=pull_request_id)
44 44
45 45 response = api_call(self.app, params)
46 46
47 47 # The above api call detaches the pull request DB object from the
48 48 # session because of an unconditional transaction rollback in our
49 49 # middleware. Therefore we need to add it back here if we want to use
50 50 # it.
51 51 Session().add(pull_request)
52 52
53 53 expected = 'merge not possible for following reasons: ' \
54 54 'Pull request reviewer approval is pending.'
55 55 assert_error(id_, expected, given=response.body)
56 56
57 57 @pytest.mark.backends("git", "hg")
58 58 def test_api_merge_pull_request(self, pr_util, no_notifications):
59 59 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
60 60 author = pull_request.user_id
61 61 repo = pull_request.target_repo.repo_id
62 62 pull_request_id = pull_request.pull_request_id
63 63 pull_request_repo = pull_request.target_repo.repo_name
64 64
65 65 id_, params = build_data(
66 66 self.apikey, 'comment_pull_request',
67 67 repoid=pull_request_repo,
68 68 pullrequestid=pull_request_id,
69 69 status='approved')
70 70
71 71 response = api_call(self.app, params)
72 72 expected = {
73 73 'comment_id': response.json.get('result', {}).get('comment_id'),
74 74 'pull_request_id': pull_request_id,
75 75 'status': {'given': 'approved', 'was_changed': True}
76 76 }
77 77 assert_ok(id_, expected, given=response.body)
78 78
79 79 id_, params = build_data(
80 80 self.apikey, 'merge_pull_request',
81 81 repoid=pull_request_repo,
82 82 pullrequestid=pull_request_id)
83 83
84 84 response = api_call(self.app, params)
85 85
86 86 pull_request = PullRequest.get(pull_request_id)
87 87
88 88 expected = {
89 89 'executed': True,
90 90 'failure_reason': 0,
91 91 'possible': True,
92 92 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
93 93 'merge_ref': pull_request.shadow_merge_ref._asdict()
94 94 }
95 95
96 96 assert_ok(id_, expected, response.body)
97 97
98 98 journal = UserLog.query()\
99 99 .filter(UserLog.user_id == author)\
100 100 .filter(UserLog.repository_id == repo) \
101 101 .order_by('user_log_id') \
102 102 .all()
103 103 assert journal[-2].action == 'repo.pull_request.merge'
104 104 assert journal[-1].action == 'repo.pull_request.close'
105 105
106 106 id_, params = build_data(
107 107 self.apikey, 'merge_pull_request',
108 108 repoid=pull_request_repo, pullrequestid=pull_request_id)
109 109 response = api_call(self.app, params)
110 110
111 111 expected = 'merge not possible for following reasons: This pull request is closed.'
112 112 assert_error(id_, expected, given=response.body)
113 113
114 114 @pytest.mark.backends("git", "hg")
115 115 def test_api_merge_pull_request_repo_error(self, pr_util):
116 116 pull_request = pr_util.create_pull_request()
117 117 id_, params = build_data(
118 118 self.apikey, 'merge_pull_request',
119 119 repoid=666, pullrequestid=pull_request.pull_request_id)
120 120 response = api_call(self.app, params)
121 121
122 122 expected = 'repository `666` does not exist'
123 123 assert_error(id_, expected, given=response.body)
124 124
125 125 @pytest.mark.backends("git", "hg")
126 126 def test_api_merge_pull_request_non_admin_with_userid_error(self,
127 127 pr_util):
128 128 pull_request = pr_util.create_pull_request(mergeable=True)
129 129 id_, params = build_data(
130 130 self.apikey_regular, 'merge_pull_request',
131 131 repoid=pull_request.target_repo.repo_name,
132 132 pullrequestid=pull_request.pull_request_id,
133 133 userid=TEST_USER_ADMIN_LOGIN)
134 134 response = api_call(self.app, params)
135 135
136 136 expected = 'userid is not the same as your user'
137 137 assert_error(id_, expected, given=response.body)
@@ -1,55 +1,55 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 os
22 22 import mock
23 23 import pytest
24 24
25 25 from rhodecode.tests import TESTS_TMP_PATH
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 TestPull(object):
32 32
33 33 @pytest.mark.backends("git", "hg")
34 34 def test_api_pull(self, backend):
35 35 r = backend.create_repo()
36 36 repo_name = r.repo_name
37 37 clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
38 38 r.clone_uri = clone_uri
39 39
40 40 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
41 41 with mock.patch('rhodecode.model.scm.url_validator'):
42 42 response = api_call(self.app, params)
43 43 msg = 'Pulled from url `%s` on repo `%s`' % (
44 44 clone_uri, repo_name)
45 45 expected = {'msg': msg,
46 46 'repository': repo_name}
47 47 assert_ok(id_, expected, given=response.body)
48 48
49 49 def test_api_pull_error(self, backend):
50 50 id_, params = build_data(
51 51 self.apikey, 'pull', repoid=backend.repo_name)
52 52 response = api_call(self.app, params)
53 53
54 54 expected = 'Unable to pull changes from `None`'
55 55 assert_error(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.db import Repository, RepositoryField
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestRemoveFieldFromRepo(object):
29 29 def test_api_remove_field_from_repo(self, backend):
30 30 repo = backend.create_repo()
31 31 repo_name = repo.repo_name
32 32
33 33 id_, params = build_data(
34 34 self.apikey, 'add_field_to_repo',
35 35 repoid=repo_name,
36 36 key='extra_field',
37 37 label='extra_field_label',
38 38 description='extra_field_desc')
39 39 response = api_call(self.app, params)
40 40 expected = {
41 41 'msg': 'Added new repository field `extra_field`',
42 42 'success': True,
43 43 }
44 44 assert_ok(id_, expected, given=response.body)
45 45
46 46 repo = Repository.get_by_repo_name(repo_name)
47 47 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
48 48 _data = repo_field.get_dict()
49 49 assert _data['field_desc'] == 'extra_field_desc'
50 50 assert _data['field_key'] == 'extra_field'
51 51 assert _data['field_label'] == 'extra_field_label'
52 52
53 53 id_, params = build_data(
54 54 self.apikey, 'remove_field_from_repo',
55 55 repoid=repo_name,
56 56 key='extra_field')
57 57 response = api_call(self.app, params)
58 58 expected = {
59 59 'msg': 'Deleted repository field `extra_field`',
60 60 'success': True,
61 61 }
62 62 assert_ok(id_, expected, given=response.body)
63 63 repo = Repository.get_by_repo_name(repo_name)
64 64 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
65 65
66 66 assert repo_field is None
@@ -1,58 +1,58 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.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 TestRemoveUserFromUserGroup(object):
31 31 def test_api_remove_user_from_user_group(self, user_util):
32 32 user, group = user_util.create_user_with_group()
33 33 user_name = user.username
34 34 group_name = group.users_group_name
35 35 id_, params = build_data(
36 36 self.apikey, 'remove_user_from_user_group',
37 37 usergroupid=group_name,
38 38 userid=user.username)
39 39 response = api_call(self.app, params)
40 40
41 41 expected = {
42 42 'msg': 'removed member `%s` from user group `%s`' % (
43 43 user_name, group_name
44 44 ),
45 45 'success': True}
46 46 assert_ok(id_, expected, given=response.body)
47 47
48 48 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
49 49 def test_api_remove_user_from_user_group_exception_occurred(
50 50 self, user_util):
51 51 user, group = user_util.create_user_with_group()
52 52 id_, params = build_data(
53 53 self.apikey, 'remove_user_from_user_group',
54 54 usergroupid=group.users_group_name, userid=user.username)
55 55 response = api_call(self.app, params)
56 56 expected = 'failed to remove member from user group `%s`' % (
57 57 group.users_group_name)
58 58 assert_error(id_, expected, given=response.body)
@@ -1,184 +1,184 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 mock
23 23 import pytest
24 24
25 25 from rhodecode.model.db import Repository
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.lib.ext_json import json
28 28 from rhodecode.lib.utils2 import time_to_datetime
29 29 from rhodecode.api.tests.utils import (
30 30 build_data, api_call, assert_ok, assert_error, crash)
31 31 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
32 32
33 33
34 34 @pytest.mark.usefixtures("testuser_api", "app")
35 35 class TestLock(object):
36 36 def test_api_lock_repo_lock_aquire(self, backend):
37 37 id_, params = build_data(
38 38 self.apikey, 'lock',
39 39 userid=TEST_USER_ADMIN_LOGIN,
40 40 repoid=backend.repo_name,
41 41 locked=True)
42 42 response = api_call(self.app, params)
43 43 expected = {
44 44 'repo': backend.repo_name, 'locked': True,
45 45 'locked_since': response.json['result']['locked_since'],
46 46 'locked_by': TEST_USER_ADMIN_LOGIN,
47 47 'lock_state_changed': True,
48 48 'lock_reason': Repository.LOCK_API,
49 49 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
50 50 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
51 51 }
52 52 assert_ok(id_, expected, given=response.body)
53 53
54 54 def test_repo_lock_aquire_by_non_admin(self, backend):
55 55 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
56 56 repo_name = repo.repo_name
57 57 id_, params = build_data(
58 58 self.apikey_regular, 'lock',
59 59 repoid=repo_name,
60 60 locked=True)
61 61 response = api_call(self.app, params)
62 62 expected = {
63 63 'repo': repo_name,
64 64 'locked': True,
65 65 'locked_since': response.json['result']['locked_since'],
66 66 'locked_by': self.TEST_USER_LOGIN,
67 67 'lock_state_changed': True,
68 68 'lock_reason': Repository.LOCK_API,
69 69 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
70 70 % (self.TEST_USER_LOGIN, repo_name, True))
71 71 }
72 72 assert_ok(id_, expected, given=response.body)
73 73
74 74 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend):
75 75 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
76 76 repo_name = repo.repo_name
77 77 id_, params = build_data(
78 78 self.apikey_regular, 'lock',
79 79 userid=TEST_USER_ADMIN_LOGIN,
80 80 repoid=repo_name,
81 81 locked=True)
82 82 response = api_call(self.app, params)
83 83 expected = 'userid is not the same as your user'
84 84 assert_error(id_, expected, given=response.body)
85 85
86 86 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend):
87 87 id_, params = build_data(
88 88 self.apikey_regular, 'lock',
89 89 repoid=backend.repo_name,
90 90 locked=True)
91 91 response = api_call(self.app, params)
92 92 expected = 'repository `%s` does not exist' % (backend.repo_name, )
93 93 assert_error(id_, expected, given=response.body)
94 94
95 95 def test_api_lock_repo_lock_release(self, backend):
96 96 id_, params = build_data(
97 97 self.apikey, 'lock',
98 98 userid=TEST_USER_ADMIN_LOGIN,
99 99 repoid=backend.repo_name,
100 100 locked=False)
101 101 response = api_call(self.app, params)
102 102 expected = {
103 103 'repo': backend.repo_name,
104 104 'locked': False,
105 105 'locked_since': None,
106 106 'locked_by': TEST_USER_ADMIN_LOGIN,
107 107 'lock_state_changed': True,
108 108 'lock_reason': Repository.LOCK_API,
109 109 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
110 110 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False))
111 111 }
112 112 assert_ok(id_, expected, given=response.body)
113 113
114 114 def test_api_lock_repo_lock_aquire_optional_userid(self, backend):
115 115 id_, params = build_data(
116 116 self.apikey, 'lock',
117 117 repoid=backend.repo_name,
118 118 locked=True)
119 119 response = api_call(self.app, params)
120 120 time_ = response.json['result']['locked_since']
121 121 expected = {
122 122 'repo': backend.repo_name,
123 123 'locked': True,
124 124 'locked_since': time_,
125 125 'locked_by': TEST_USER_ADMIN_LOGIN,
126 126 'lock_state_changed': True,
127 127 'lock_reason': Repository.LOCK_API,
128 128 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
129 129 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
130 130 }
131 131
132 132 assert_ok(id_, expected, given=response.body)
133 133
134 134 def test_api_lock_repo_lock_optional_locked(self, backend):
135 135 # TODO: Provide a fixture locked_repository or similar
136 136 repo = Repository.get_by_repo_name(backend.repo_name)
137 137 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
138 138 Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API)
139 139
140 140 id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name)
141 141 response = api_call(self.app, params)
142 142 time_ = response.json['result']['locked_since']
143 143 expected = {
144 144 'repo': backend.repo_name,
145 145 'locked': True,
146 146 'locked_since': time_,
147 147 'locked_by': TEST_USER_ADMIN_LOGIN,
148 148 'lock_state_changed': False,
149 149 'lock_reason': Repository.LOCK_API,
150 150 'msg': ('Repo `%s` locked by `%s` on `%s`.'
151 151 % (backend.repo_name, TEST_USER_ADMIN_LOGIN,
152 152 json.dumps(time_to_datetime(time_))))
153 153 }
154 154 assert_ok(id_, expected, given=response.body)
155 155
156 156 def test_api_lock_repo_lock_optional_not_locked(self, backend):
157 157 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
158 158 repo_name = repo.repo_name
159 159 assert repo.locked == [None, None, None]
160 160 id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id)
161 161 response = api_call(self.app, params)
162 162 expected = {
163 163 'repo': repo_name,
164 164 'locked': False,
165 165 'locked_since': None,
166 166 'locked_by': None,
167 167 'lock_state_changed': False,
168 168 'lock_reason': None,
169 169 'msg': ('Repo `%s` not locked.' % (repo_name,))
170 170 }
171 171 assert_ok(id_, expected, given=response.body)
172 172
173 173 @mock.patch.object(Repository, 'lock', crash)
174 174 def test_api_lock_error(self, backend):
175 175 id_, params = build_data(
176 176 self.apikey, 'lock',
177 177 userid=TEST_USER_ADMIN_LOGIN,
178 178 repoid=backend.repo_name,
179 179 locked=True)
180 180 response = api_call(self.app, params)
181 181
182 182 expected = 'Error occurred locking repository `%s`' % (
183 183 backend.repo_name,)
184 184 assert_error(id_, expected, given=response.body)
@@ -1,44 +1,44 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.scm import ScmModel
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 TestRescanRepos(object):
31 31 def test_api_rescan_repos(self):
32 32 id_, params = build_data(self.apikey, 'rescan_repos')
33 33 response = api_call(self.app, params)
34 34
35 35 expected = {'added': [], 'removed': []}
36 36 assert_ok(id_, expected, given=response.body)
37 37
38 38 @mock.patch.object(ScmModel, 'repo_scan', crash)
39 39 def test_api_rescann_error(self):
40 40 id_, params = build_data(self.apikey, 'rescan_repos', )
41 41 response = api_call(self.app, params)
42 42
43 43 expected = 'Error occurred during rescan repositories action'
44 44 assert_error(id_, expected, given=response.body)
@@ -1,67 +1,67 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.repo import RepoModel
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 TestRevokeUserGroupPermission(object):
31 31 def test_api_revoke_user_group_permission(self, backend, user_util):
32 32 repo = backend.create_repo()
33 33 user_group = user_util.create_user_group()
34 34 user_util.grant_user_group_permission_to_repo(
35 35 repo, user_group, 'repository.read')
36 36 id_, params = build_data(
37 37 self.apikey,
38 38 'revoke_user_group_permission',
39 39 repoid=backend.repo_name,
40 40 usergroupid=user_group.users_group_name)
41 41 response = api_call(self.app, params)
42 42
43 43 expected = {
44 44 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
45 45 user_group.users_group_name, 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_group_permission', crash)
52 52 def test_api_revoke_user_group_permission_exception_when_adding(
53 53 self, backend, user_util):
54 54 user_group = user_util.create_user_group()
55 55 id_, params = build_data(
56 56 self.apikey,
57 57 'revoke_user_group_permission',
58 58 repoid=backend.repo_name,
59 59 usergroupid=user_group.users_group_name)
60 60 response = api_call(self.app, params)
61 61
62 62 expected = (
63 63 'failed to edit permission for user group: `%s` in repo: `%s`' % (
64 64 user_group.users_group_name, backend.repo_name
65 65 )
66 66 )
67 67 assert_error(id_, expected, given=response.body)
@@ -1,129 +1,129 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.repo_group import RepoGroupModel
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, crash)
28 28
29 29
30 30 @pytest.mark.usefixtures("testuser_api", "app")
31 31 class TestRevokeUserGroupPermissionFromRepoGroup(object):
32 32 @pytest.mark.parametrize("name, apply_to_children", [
33 33 ('none', 'none'),
34 34 ('all', 'all'),
35 35 ('repos', 'repos'),
36 36 ('groups', 'groups'),
37 37 ])
38 38 def test_api_revoke_user_group_permission_from_repo_group(
39 39 self, name, apply_to_children, user_util):
40 40 user_group = user_util.create_user_group()
41 41 repo_group = user_util.create_repo_group()
42 42 user_util.grant_user_group_permission_to_repo_group(
43 43 repo_group, user_group, 'group.read')
44 44
45 45 id_, params = build_data(
46 46 self.apikey, 'revoke_user_group_permission_from_repo_group',
47 47 repogroupid=repo_group.name,
48 48 usergroupid=user_group.users_group_name,
49 49 apply_to_children=apply_to_children,)
50 50 response = api_call(self.app, params)
51 51
52 52 expected = {
53 53 'msg': (
54 54 'Revoked perm (recursive:%s) for user group: `%s`'
55 55 ' in repo group: `%s`' % (
56 56 apply_to_children, user_group.users_group_name,
57 57 repo_group.name
58 58 )
59 59 ),
60 60 'success': True
61 61 }
62 62 assert_ok(id_, expected, given=response.body)
63 63
64 64 @pytest.mark.parametrize(
65 65 "name, apply_to_children, grant_admin, access_ok", [
66 66 ('none', 'none', False, False),
67 67 ('all', 'all', False, False),
68 68 ('repos', 'repos', False, False),
69 69 ('groups', 'groups', False, False),
70 70
71 71 # after granting admin rights
72 72 ('none', 'none', False, False),
73 73 ('all', 'all', False, False),
74 74 ('repos', 'repos', False, False),
75 75 ('groups', 'groups', False, False),
76 76 ]
77 77 )
78 78 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
79 79 self, name, apply_to_children, grant_admin, access_ok, user_util):
80 80 user_group = user_util.create_user_group()
81 81 repo_group = user_util.create_repo_group()
82 82 user_util.grant_user_group_permission_to_repo_group(
83 83 repo_group, user_group, 'group.read')
84 84
85 85 if grant_admin:
86 86 user_util.grant_user_permission_to_repo_group(
87 87 repo_group.name, self.TEST_USER_LOGIN, 'group.admin')
88 88
89 89 id_, params = build_data(
90 90 self.apikey_regular,
91 91 'revoke_user_group_permission_from_repo_group',
92 92 repogroupid=repo_group.name,
93 93 usergroupid=user_group.users_group_name,
94 94 apply_to_children=apply_to_children,)
95 95 response = api_call(self.app, params)
96 96 if access_ok:
97 97 expected = {
98 98 'msg': (
99 99 'Revoked perm (recursive:%s) for user group: `%s`'
100 100 ' in repo group: `%s`' % (
101 101 apply_to_children, TEST_USER_ADMIN_LOGIN,
102 102 repo_group.name
103 103 )
104 104 ),
105 105 'success': True
106 106 }
107 107 assert_ok(id_, expected, given=response.body)
108 108 else:
109 109 expected = 'repository group `%s` does not exist' % (
110 110 repo_group.name,)
111 111 assert_error(id_, expected, given=response.body)
112 112
113 113 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
114 114 def test_api_revoke_user_group_permission_from_repo_group_exception_on_add(
115 115 self, user_util):
116 116 user_group = user_util.create_user_group()
117 117 repo_group = user_util.create_repo_group()
118 118 id_, params = build_data(
119 119 self.apikey, 'revoke_user_group_permission_from_repo_group',
120 120 repogroupid=repo_group.name,
121 121 usergroupid=user_group.users_group_name)
122 122 response = api_call(self.app, params)
123 123
124 124 expected = (
125 125 'failed to edit permission for user group: `%s`'
126 126 ' in repo group: `%s`' % (
127 127 user_group.users_group_name, repo_group.name)
128 128 )
129 129 assert_error(id_, expected, given=response.body)
@@ -1,58 +1,58 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.model.user import UserModel
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestRevokeUserGroupPermissionFromUserGroup(object):
29 29 @pytest.mark.parametrize("name", [
30 30 ('none',),
31 31 ('all',),
32 32 ('repos',),
33 33 ('groups',),
34 34 ])
35 35 def test_api_revoke_user_group_permission_from_user_group(
36 36 self, name, user_util):
37 37 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
38 38 group = user_util.create_user_group()
39 39 source_group = user_util.create_user_group()
40 40
41 41 user_util.grant_user_permission_to_user_group(
42 42 group, user, 'usergroup.read')
43 43 user_util.grant_user_group_permission_to_user_group(
44 44 source_group, group, 'usergroup.read')
45 45
46 46 id_, params = build_data(
47 47 self.apikey, 'revoke_user_group_permission_from_user_group',
48 48 usergroupid=group.users_group_name,
49 49 sourceusergroupid=source_group.users_group_name)
50 50 response = api_call(self.app, params)
51 51
52 52 expected = {
53 53 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % (
54 54 source_group.users_group_name, group.users_group_name
55 55 ),
56 56 'success': True
57 57 }
58 58 assert_ok(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.repo import RepoModel
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 TestRevokeUserPermission(object):
31 31 def test_api_revoke_user_permission(self, backend, user_util):
32 32 repo = backend.create_repo()
33 33 user = user_util.create_user()
34 34 user_util.grant_user_permission_to_repo(
35 35 repo, user, 'repository.read')
36 36
37 37 id_, params = build_data(
38 38 self.apikey,
39 39 'revoke_user_permission',
40 40 repoid=repo.repo_name,
41 41 userid=user.username)
42 42 response = api_call(self.app, params)
43 43
44 44 expected = {
45 45 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
46 46 user.username, backend.repo_name
47 47 ),
48 48 'success': True
49 49 }
50 50 assert_ok(id_, expected, given=response.body)
51 51
52 52 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
53 53 def test_api_revoke_user_permission_exception_when_adding(
54 54 self, backend, user_util):
55 55 user = user_util.create_user()
56 56 id_, params = build_data(
57 57 self.apikey,
58 58 'revoke_user_permission',
59 59 repoid=backend.repo_name,
60 60 userid=user.username)
61 61 response = api_call(self.app, params)
62 62
63 63 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
64 64 user.username, backend.repo_name
65 65 )
66 66 assert_error(id_, expected, given=response.body)
@@ -1,126 +1,126 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.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 TestRevokeUserPermissionFromRepoGroup(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_permission_from_repo_group(
38 38 self, name, apply_to_children, user_util):
39 39 user = user_util.create_user()
40 40 repo_group = user_util.create_repo_group()
41 41 user_util.grant_user_permission_to_repo_group(
42 42 repo_group, user, 'group.read')
43 43
44 44 id_, params = build_data(
45 45 self.apikey,
46 46 'revoke_user_permission_from_repo_group',
47 47 repogroupid=repo_group.name,
48 48 userid=user.username,
49 49 apply_to_children=apply_to_children,)
50 50 response = api_call(self.app, params)
51 51
52 52 expected = {
53 53 'msg': (
54 54 'Revoked perm (recursive:%s) for user: `%s`'
55 55 ' in repo group: `%s`' % (
56 56 apply_to_children, user.username, 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_permission_from_repo_group_by_regular_user(
78 78 self, name, apply_to_children, grant_admin, access_ok, user_util):
79 79 user = user_util.create_user()
80 80 repo_group = user_util.create_repo_group()
81 81 permission = 'group.admin' if grant_admin else 'group.read'
82 82 user_util.grant_user_permission_to_repo_group(
83 83 repo_group, user, permission)
84 84
85 85 id_, params = build_data(
86 86 self.apikey_regular,
87 87 'revoke_user_permission_from_repo_group',
88 88 repogroupid=repo_group.name,
89 89 userid=user.username,
90 90 apply_to_children=apply_to_children,)
91 91 response = api_call(self.app, params)
92 92 if access_ok:
93 93 expected = {
94 94 'msg': (
95 95 'Revoked perm (recursive:%s) for user: `%s`'
96 96 ' in repo group: `%s`' % (
97 97 apply_to_children, user.username, repo_group.name
98 98 )
99 99 ),
100 100 'success': True
101 101 }
102 102 assert_ok(id_, expected, given=response.body)
103 103 else:
104 104 expected = 'repository group `%s` does not exist' % (
105 105 repo_group.name)
106 106 assert_error(id_, expected, given=response.body)
107 107
108 108 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
109 109 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(
110 110 self, user_util):
111 111 user = user_util.create_user()
112 112 repo_group = user_util.create_repo_group()
113 113 id_, params = build_data(
114 114 self.apikey,
115 115 'revoke_user_permission_from_repo_group',
116 116 repogroupid=repo_group.name,
117 117 userid=user.username
118 118 )
119 119 response = api_call(self.app, params)
120 120
121 121 expected = (
122 122 'failed to edit permission for user: `%s` in repo group: `%s`' % (
123 123 user.username, repo_group.name
124 124 )
125 125 )
126 126 assert_error(id_, expected, given=response.body)
@@ -1,112 +1,112 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.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 TestRevokeUserPermissionFromUserGroup(object):
31 31 @pytest.mark.parametrize("name", [
32 32 ('none',),
33 33 ('all',),
34 34 ('repos',),
35 35 ('groups',),
36 36 ])
37 37 def test_api_revoke_user_permission_from_user_group(self, name, user_util):
38 38 user = user_util.create_user()
39 39 group = user_util.create_user_group()
40 40 user_util.grant_user_permission_to_user_group(
41 41 group, user, 'usergroup.admin')
42 42
43 43 id_, params = build_data(
44 44 self.apikey,
45 45 'revoke_user_permission_from_user_group',
46 46 usergroupid=group.users_group_name,
47 47 userid=user.username)
48 48 response = api_call(self.app, params)
49 49
50 50 expected = {
51 51 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
52 52 user.username, group.users_group_name
53 53 ),
54 54 'success': True
55 55 }
56 56 assert_ok(id_, expected, given=response.body)
57 57
58 58 @pytest.mark.parametrize("name, grant_admin, access_ok", [
59 59 ('none', False, False),
60 60 ('all', False, False),
61 61 ('repos', False, False),
62 62 ('groups', False, False),
63 63
64 64 # after granting admin rights
65 65 ('none', False, False),
66 66 ('all', False, False),
67 67 ('repos', False, False),
68 68 ('groups', False, False),
69 69 ])
70 70 def test_api_revoke_user_permission_from_user_group_by_regular_user(
71 71 self, name, grant_admin, access_ok, user_util):
72 72 user = user_util.create_user()
73 73 group = user_util.create_user_group()
74 74 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
75 75 user_util.grant_user_permission_to_user_group(group, user, permission)
76 76
77 77 id_, params = build_data(
78 78 self.apikey_regular,
79 79 'revoke_user_permission_from_user_group',
80 80 usergroupid=group.users_group_name,
81 81 userid=user.username)
82 82 response = api_call(self.app, params)
83 83 if access_ok:
84 84 expected = {
85 85 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
86 86 user.username, group.users_group_name
87 87 ),
88 88 'success': True
89 89 }
90 90 assert_ok(id_, expected, given=response.body)
91 91 else:
92 92 expected = 'user group `%s` does not exist' % (
93 93 group.users_group_name)
94 94 assert_error(id_, expected, given=response.body)
95 95
96 96 @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash)
97 97 def test_api_revoke_user_permission_from_user_group_exception_when_adding(
98 98 self, user_util):
99 99 user = user_util.create_user()
100 100 group = user_util.create_user_group()
101 101 id_, params = build_data(
102 102 self.apikey,
103 103 'revoke_user_permission_from_user_group',
104 104 usergroupid=group.users_group_name,
105 105 userid=user.username)
106 106 response = api_call(self.app, params)
107 107
108 108 expected = (
109 109 'failed to edit permission for user: `%s` in user group: `%s`' % (
110 110 user.username, group.users_group_name)
111 111 )
112 112 assert_error(id_, expected, given=response.body)
@@ -1,59 +1,59 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23
24 24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, assert_error
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestStoreException(object):
29 29
30 30 def test_store_exception_invalid_json(self):
31 31 id_, params = build_data(self.apikey, 'store_exception',
32 32 exc_data_json='XXX,{')
33 33 response = api_call(self.app, params)
34 34
35 35 expected = 'Failed to parse JSON data from exc_data_json field. ' \
36 36 'Please make sure it contains a valid JSON.'
37 37 assert_error(id_, expected, given=response.body)
38 38
39 39 def test_store_exception_missing_json_params_json(self):
40 40 id_, params = build_data(self.apikey, 'store_exception',
41 41 exc_data_json='{"foo":"bar"}')
42 42 response = api_call(self.app, params)
43 43
44 44 expected = "Missing exc_traceback, or exc_type_name in " \
45 45 "exc_data_json field. Missing: 'exc_traceback'"
46 46 assert_error(id_, expected, given=response.body)
47 47
48 48 def test_store_exception(self):
49 49 id_, params = build_data(
50 50 self.apikey, 'store_exception',
51 51 exc_data_json='{"exc_traceback": "invalid", "exc_type_name":"ValueError"}')
52 52 response = api_call(self.app, params)
53 53 exc_id = response.json['result']['exc_id']
54 54
55 55 expected = {
56 56 'exc_id': exc_id,
57 57 'exc_url': 'http://example.com/_admin/settings/exceptions/{}'.format(exc_id)
58 58 }
59 59 assert_ok(id_, expected, given=response.body)
@@ -1,212 +1,212 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import pytest
22 22
23 23 from rhodecode.lib.vcs.nodes import FileNode
24 24 from rhodecode.model.db import User
25 25 from rhodecode.model.pull_request import PullRequestModel
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)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestUpdatePullRequest(object):
33 33
34 34 @pytest.mark.backends("git", "hg")
35 35 def test_api_update_pull_request_title_or_description(
36 36 self, pr_util, no_notifications):
37 37 pull_request = pr_util.create_pull_request()
38 38
39 39 id_, params = build_data(
40 40 self.apikey, 'update_pull_request',
41 41 repoid=pull_request.target_repo.repo_name,
42 42 pullrequestid=pull_request.pull_request_id,
43 43 title='New TITLE OF A PR',
44 44 description='New DESC OF A PR',
45 45 )
46 46 response = api_call(self.app, params)
47 47
48 48 expected = {
49 49 "msg": "Updated pull request `{}`".format(
50 50 pull_request.pull_request_id),
51 51 "pull_request": response.json['result']['pull_request'],
52 52 "updated_commits": {"added": [], "common": [], "removed": []},
53 53 "updated_reviewers": {"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('file_b', 'test_content\n')]},
85 85 {'message': 'c', 'added': [FileNode('file_c', '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 }
115 115
116 116 assert_ok(id_, expected, response.body)
117 117
118 118 @pytest.mark.backends("git", "hg")
119 119 def test_api_update_change_reviewers(
120 120 self, user_util, pr_util, no_notifications):
121 121 a = user_util.create_user()
122 122 b = user_util.create_user()
123 123 c = user_util.create_user()
124 124 new_reviewers = [
125 125 {'username': b.username,'reasons': ['updated via API'],
126 126 'mandatory':False},
127 127 {'username': c.username, 'reasons': ['updated via API'],
128 128 'mandatory':False},
129 129 ]
130 130
131 131 added = [b.username, c.username]
132 132 removed = [a.username]
133 133
134 134 pull_request = pr_util.create_pull_request(
135 135 reviewers=[(a.username, ['added via API'], False, [])])
136 136
137 137 id_, params = build_data(
138 138 self.apikey, 'update_pull_request',
139 139 repoid=pull_request.target_repo.repo_name,
140 140 pullrequestid=pull_request.pull_request_id,
141 141 reviewers=new_reviewers)
142 142 response = api_call(self.app, params)
143 143 expected = {
144 144 "msg": "Updated pull request `{}`".format(
145 145 pull_request.pull_request_id),
146 146 "pull_request": response.json['result']['pull_request'],
147 147 "updated_commits": {"added": [], "common": [], "removed": []},
148 148 "updated_reviewers": {"added": added, "removed": removed},
149 149 }
150 150
151 151 assert_ok(id_, expected, response.body)
152 152
153 153 @pytest.mark.backends("git", "hg")
154 154 def test_api_update_bad_user_in_reviewers(self, pr_util):
155 155 pull_request = pr_util.create_pull_request()
156 156
157 157 id_, params = build_data(
158 158 self.apikey, 'update_pull_request',
159 159 repoid=pull_request.target_repo.repo_name,
160 160 pullrequestid=pull_request.pull_request_id,
161 161 reviewers=[{'username': 'bad_name'}])
162 162 response = api_call(self.app, params)
163 163
164 164 expected = 'user `bad_name` does not exist'
165 165
166 166 assert_error(id_, expected, response.body)
167 167
168 168 @pytest.mark.backends("git", "hg")
169 169 def test_api_update_repo_error(self, pr_util):
170 170 pull_request = pr_util.create_pull_request()
171 171 id_, params = build_data(
172 172 self.apikey, 'update_pull_request',
173 173 repoid='fake',
174 174 pullrequestid=pull_request.pull_request_id,
175 175 reviewers=[{'username': 'bad_name'}])
176 176 response = api_call(self.app, params)
177 177
178 178 expected = 'repository `fake` does not exist'
179 179
180 180 response_json = response.json['error']
181 181 assert response_json == expected
182 182
183 183 @pytest.mark.backends("git", "hg")
184 184 def test_api_update_pull_request_error(self, pr_util):
185 185 pull_request = pr_util.create_pull_request()
186 186
187 187 id_, params = build_data(
188 188 self.apikey, 'update_pull_request',
189 189 repoid=pull_request.target_repo.repo_name,
190 190 pullrequestid=999999,
191 191 reviewers=[{'username': 'bad_name'}])
192 192 response = api_call(self.app, params)
193 193
194 194 expected = 'pull request `999999` does not exist'
195 195 assert_error(id_, expected, response.body)
196 196
197 197 @pytest.mark.backends("git", "hg")
198 198 def test_api_update_pull_request_no_perms_to_update(
199 199 self, user_util, pr_util):
200 200 user = user_util.create_user()
201 201 pull_request = pr_util.create_pull_request()
202 202
203 203 id_, params = build_data(
204 204 user.api_key, 'update_pull_request',
205 205 repoid=pull_request.target_repo.repo_name,
206 206 pullrequestid=pull_request.pull_request_id,)
207 207 response = api_call(self.app, params)
208 208
209 209 expected = ('pull request `%s` update failed, '
210 210 'no permission to update.') % pull_request.pull_request_id
211 211
212 212 assert_error(id_, expected, response.body)
@@ -1,203 +1,203 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.repo import RepoModel
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.plugin 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': 'rev:tip'},
69 69 {'landing_rev': ['rev', 'tip']}),
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 expected_api_data = repo.get_api_data(include_secrets=True)
101 101 if expected is SAME_AS_UPDATES:
102 102 expected_api_data.update(updates)
103 103 else:
104 104 expected_api_data.update(expected)
105 105
106 106 id_, params = build_data(
107 107 self.apikey, 'update_repo', repoid=repo_name, **updates)
108 108
109 109 with mock.patch('rhodecode.model.validation_schema.validators.url_validator'):
110 110 response = api_call(self.app, params)
111 111
112 112 if updates.get('repo_name'):
113 113 repo_name = updates['repo_name']
114 114
115 115 try:
116 116 expected = {
117 117 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
118 118 'repository': jsonify(expected_api_data)
119 119 }
120 120 assert_ok(id_, expected, given=response.body)
121 121 finally:
122 122 fixture.destroy_repo(repo_name)
123 123 if updates.get('_group'):
124 124 fixture.destroy_repo_group(updates['_group'])
125 125
126 126 def test_api_update_repo_fork_of_field(self, backend):
127 127 master_repo = backend.create_repo()
128 128 repo = backend.create_repo()
129 129 updates = {
130 130 'fork_of': master_repo.repo_name,
131 131 'fork_of_id': master_repo.repo_id
132 132 }
133 133 expected_api_data = repo.get_api_data(include_secrets=True)
134 134 expected_api_data.update(updates)
135 135
136 136 id_, params = build_data(
137 137 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
138 138 response = api_call(self.app, params)
139 139 expected = {
140 140 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
141 141 'repository': jsonify(expected_api_data)
142 142 }
143 143 assert_ok(id_, expected, given=response.body)
144 144 result = response.json['result']['repository']
145 145 assert result['fork_of'] == master_repo.repo_name
146 146 assert result['fork_of_id'] == master_repo.repo_id
147 147
148 148 def test_api_update_repo_fork_of_not_found(self, backend):
149 149 master_repo_name = 'fake-parent-repo'
150 150 repo = backend.create_repo()
151 151 updates = {
152 152 'fork_of': master_repo_name
153 153 }
154 154 id_, params = build_data(
155 155 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
156 156 response = api_call(self.app, params)
157 157 expected = {
158 158 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
159 159 master_repo_name)}
160 160 assert_error(id_, expected, given=response.body)
161 161
162 162 def test_api_update_repo_with_repo_group_not_existing(self):
163 163 repo_name = 'admin_owned'
164 164 fake_repo_group = 'test_group_for_update'
165 165 fixture.create_repo(repo_name)
166 166 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
167 167 id_, params = build_data(
168 168 self.apikey, 'update_repo', repoid=repo_name, **updates)
169 169 response = api_call(self.app, params)
170 170 try:
171 171 expected = {
172 172 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
173 173 }
174 174 assert_error(id_, expected, given=response.body)
175 175 finally:
176 176 fixture.destroy_repo(repo_name)
177 177
178 178 def test_api_update_repo_regular_user_not_allowed(self):
179 179 repo_name = 'admin_owned'
180 180 fixture.create_repo(repo_name)
181 181 updates = {'active': False}
182 182 id_, params = build_data(
183 183 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
184 184 response = api_call(self.app, params)
185 185 try:
186 186 expected = 'repository `%s` does not exist' % (repo_name,)
187 187 assert_error(id_, expected, given=response.body)
188 188 finally:
189 189 fixture.destroy_repo(repo_name)
190 190
191 191 @mock.patch.object(RepoModel, 'update', crash)
192 192 def test_api_update_repo_exception_occurred(self, backend):
193 193 repo_name = UPDATE_REPO_NAME
194 194 fixture.create_repo(repo_name, repo_type=backend.alias)
195 195 id_, params = build_data(
196 196 self.apikey, 'update_repo', repoid=repo_name,
197 197 owner=TEST_USER_ADMIN_LOGIN,)
198 198 response = api_call(self.app, params)
199 199 try:
200 200 expected = 'failed to update repo `%s`' % (repo_name,)
201 201 assert_error(id_, expected, given=response.body)
202 202 finally:
203 203 fixture.destroy_repo(repo_name)
@@ -1,150 +1,150 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 os
22 22
23 23 import pytest
24 24
25 25 from rhodecode.model.repo_group import RepoGroupModel
26 26 from rhodecode.model.user import UserModel
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestApiUpdateRepoGroup(object):
33 33
34 34 def test_update_group_name(self, user_util):
35 35 new_group_name = 'new-group'
36 36 initial_name = self._update(user_util, group_name=new_group_name)
37 37 assert RepoGroupModel()._get_repo_group(initial_name) is None
38 38 new_group = RepoGroupModel()._get_repo_group(new_group_name)
39 39 assert new_group is not None
40 40 assert new_group.full_path == new_group_name
41 41
42 42 def test_update_group_name_change_parent(self, user_util):
43 43
44 44 parent_group = user_util.create_repo_group()
45 45 parent_group_name = parent_group.name
46 46
47 47 expected_group_name = '{}/{}'.format(parent_group_name, 'new-group')
48 48 initial_name = self._update(user_util, group_name=expected_group_name)
49 49
50 50 repo_group = RepoGroupModel()._get_repo_group(expected_group_name)
51 51
52 52 assert repo_group is not None
53 53 assert repo_group.group_name == expected_group_name
54 54 assert repo_group.full_path == expected_group_name
55 55 assert RepoGroupModel()._get_repo_group(initial_name) is None
56 56
57 57 new_path = os.path.join(
58 58 RepoGroupModel().repos_path, *repo_group.full_path_splitted)
59 59 assert os.path.exists(new_path)
60 60
61 61 def test_update_enable_locking(self, user_util):
62 62 initial_name = self._update(user_util, enable_locking=True)
63 63 repo_group = RepoGroupModel()._get_repo_group(initial_name)
64 64 assert repo_group.enable_locking is True
65 65
66 66 def test_update_description(self, user_util):
67 67 description = 'New description'
68 68 initial_name = self._update(user_util, description=description)
69 69 repo_group = RepoGroupModel()._get_repo_group(initial_name)
70 70 assert repo_group.group_description == description
71 71
72 72 def test_update_owner(self, user_util):
73 73 owner = self.TEST_USER_LOGIN
74 74 initial_name = self._update(user_util, owner=owner)
75 75 repo_group = RepoGroupModel()._get_repo_group(initial_name)
76 76 assert repo_group.user.username == owner
77 77
78 78 def test_update_group_name_conflict_with_existing(self, user_util):
79 79 group_1 = user_util.create_repo_group()
80 80 group_2 = user_util.create_repo_group()
81 81 repo_group_name_1 = group_1.group_name
82 82 repo_group_name_2 = group_2.group_name
83 83
84 84 id_, params = build_data(
85 85 self.apikey, 'update_repo_group', repogroupid=repo_group_name_1,
86 86 group_name=repo_group_name_2)
87 87 response = api_call(self.app, params)
88 88 expected = {
89 89 'unique_repo_group_name':
90 90 'Repository group with name `{}` already exists'.format(
91 91 repo_group_name_2)}
92 92 assert_error(id_, expected, given=response.body)
93 93
94 94 def test_api_update_repo_group_by_regular_user_no_permission(self, user_util):
95 95 temp_user = user_util.create_user()
96 96 temp_user_api_key = temp_user.api_key
97 97 parent_group = user_util.create_repo_group()
98 98 repo_group_name = parent_group.group_name
99 99 id_, params = build_data(
100 100 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name)
101 101 response = api_call(self.app, params)
102 102 expected = 'repository group `%s` does not exist' % (repo_group_name,)
103 103 assert_error(id_, expected, given=response.body)
104 104
105 105 def test_api_update_repo_group_regular_user_no_root_write_permissions(
106 106 self, user_util):
107 107 temp_user = user_util.create_user()
108 108 temp_user_api_key = temp_user.api_key
109 109 parent_group = user_util.create_repo_group(owner=temp_user.username)
110 110 repo_group_name = parent_group.group_name
111 111
112 112 id_, params = build_data(
113 113 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name,
114 114 group_name='at-root-level')
115 115 response = api_call(self.app, params)
116 116 expected = {
117 117 'repo_group': 'You do not have the permission to store '
118 118 'repository groups in the root location.'}
119 119 assert_error(id_, expected, given=response.body)
120 120
121 121 def _update(self, user_util, **kwargs):
122 122 repo_group = user_util.create_repo_group()
123 123 initial_name = repo_group.name
124 124 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
125 125 user_util.grant_user_permission_to_repo_group(
126 126 repo_group, user, 'group.admin')
127 127
128 128 id_, params = build_data(
129 129 self.apikey, 'update_repo_group', repogroupid=initial_name,
130 130 **kwargs)
131 131 response = api_call(self.app, params)
132 132
133 133 repo_group = RepoGroupModel.cls.get(repo_group.group_id)
134 134
135 135 expected = {
136 136 'msg': 'updated repository group ID:{} {}'.format(
137 137 repo_group.group_id, repo_group.group_name),
138 138 'repo_group': {
139 139 'repositories': [],
140 140 'group_name': repo_group.group_name,
141 141 'group_description': repo_group.group_description,
142 142 'owner': repo_group.user.username,
143 143 'group_id': repo_group.group_id,
144 144 'parent_group': (
145 145 repo_group.parent_group.name
146 146 if repo_group.parent_group else None)
147 147 }
148 148 }
149 149 assert_ok(id_, expected, given=response.body)
150 150 return initial_name
@@ -1,120 +1,120 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.db import User
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, jsonify)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestUpdateUser(object):
33 33 @pytest.mark.parametrize("name, expected", [
34 34 ('firstname', 'new_username'),
35 35 ('lastname', 'new_username'),
36 36 ('email', 'new_username'),
37 37 ('admin', True),
38 38 ('admin', False),
39 39 ('extern_type', 'ldap'),
40 40 ('extern_type', None),
41 41 ('extern_name', 'test'),
42 42 ('extern_name', None),
43 43 ('active', False),
44 44 ('active', True),
45 45 ('password', 'newpass')
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,125 +1,125 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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.model.user import UserModel
25 25 from rhodecode.model.user_group import UserGroupModel
26 26 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_ok, crash, jsonify)
29 29
30 30
31 31 @pytest.mark.usefixtures("testuser_api", "app")
32 32 class TestUpdateUserGroup(object):
33 33 @pytest.mark.parametrize("changing_attr, updates", [
34 34 ('group_name', {'group_name': 'new_group_name'}),
35 35 ('group_name', {'group_name': 'test_group_for_update'}),
36 36 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
37 37 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
38 38 ('active', {'active': False}),
39 39 ('active', {'active': True}),
40 40 ('sync', {'sync': False}),
41 41 ('sync', {'sync': True})
42 42 ])
43 43 def test_api_update_user_group(self, changing_attr, updates, user_util):
44 44 user_group = user_util.create_user_group()
45 45 group_name = user_group.users_group_name
46 46 expected_api_data = user_group.get_api_data()
47 47 expected_api_data.update(updates)
48 48
49 49 id_, params = build_data(
50 50 self.apikey, 'update_user_group', usergroupid=group_name,
51 51 **updates)
52 52 response = api_call(self.app, params)
53 53
54 54 # special case for sync
55 55 if changing_attr == 'sync' and updates['sync'] is False:
56 56 expected_api_data['sync'] = None
57 57 elif changing_attr == 'sync' and updates['sync'] is True:
58 58 expected_api_data['sync'] = 'manual_api'
59 59
60 60 expected = {
61 61 'msg': 'updated user group ID:%s %s' % (
62 62 user_group.users_group_id, user_group.users_group_name),
63 63 'user_group': jsonify(expected_api_data)
64 64 }
65 65 assert_ok(id_, expected, given=response.body)
66 66
67 67 @pytest.mark.parametrize("changing_attr, updates", [
68 68 # TODO: mikhail: decide if we need to test against the commented params
69 69 # ('group_name', {'group_name': 'new_group_name'}),
70 70 # ('group_name', {'group_name': 'test_group_for_update'}),
71 71 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
72 72 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
73 73 ('active', {'active': False}),
74 74 ('active', {'active': True}),
75 75 ('sync', {'sync': False}),
76 76 ('sync', {'sync': True})
77 77 ])
78 78 def test_api_update_user_group_regular_user(
79 79 self, changing_attr, updates, user_util):
80 80 user_group = user_util.create_user_group()
81 81 group_name = user_group.users_group_name
82 82 expected_api_data = user_group.get_api_data()
83 83 expected_api_data.update(updates)
84 84
85 85 # grant permission to this user
86 86 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
87 87
88 88 user_util.grant_user_permission_to_user_group(
89 89 user_group, user, 'usergroup.admin')
90 90 id_, params = build_data(
91 91 self.apikey_regular, 'update_user_group',
92 92 usergroupid=group_name, **updates)
93 93 response = api_call(self.app, params)
94 94 # special case for sync
95 95 if changing_attr == 'sync' and updates['sync'] is False:
96 96 expected_api_data['sync'] = None
97 97 elif changing_attr == 'sync' and updates['sync'] is True:
98 98 expected_api_data['sync'] = 'manual_api'
99 99
100 100 expected = {
101 101 'msg': 'updated user group ID:%s %s' % (
102 102 user_group.users_group_id, user_group.users_group_name),
103 103 'user_group': jsonify(expected_api_data)
104 104 }
105 105 assert_ok(id_, expected, given=response.body)
106 106
107 107 def test_api_update_user_group_regular_user_no_permission(self, user_util):
108 108 user_group = user_util.create_user_group()
109 109 group_name = user_group.users_group_name
110 110 id_, params = build_data(
111 111 self.apikey_regular, 'update_user_group', usergroupid=group_name)
112 112 response = api_call(self.app, params)
113 113
114 114 expected = 'user group `%s` does not exist' % (group_name)
115 115 assert_error(id_, expected, given=response.body)
116 116
117 117 @mock.patch.object(UserGroupModel, 'update', crash)
118 118 def test_api_update_user_group_exception_occurred(self, user_util):
119 119 user_group = user_util.create_user_group()
120 120 group_name = user_group.users_group_name
121 121 id_, params = build_data(
122 122 self.apikey, 'update_user_group', usergroupid=group_name)
123 123 response = api_call(self.app, params)
124 124 expected = 'failed to update user group `%s`' % (group_name,)
125 125 assert_error(id_, expected, given=response.body)
@@ -1,295 +1,295 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 pytest
23 23 from mock import Mock, patch
24 24
25 25 from rhodecode.api import utils
26 26 from rhodecode.api import JSONRPCError
27 27 from rhodecode.lib.vcs.exceptions import RepositoryError
28 28
29 29
30 30 class TestGetCommitOrError(object):
31 31 def setup(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 def setup(self):
70 70 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
71 71
72 72 def test_success_with_no_hash_specified(self):
73 73 repo = Mock()
74 74 ref_type = 'branch'
75 75 ref_name = 'master'
76 76 ref = '{}:{}'.format(ref_type, ref_name)
77 77
78 78 with patch('rhodecode.api.utils._get_ref_hash') \
79 79 as _get_ref_hash:
80 80 _get_ref_hash.return_value = self.commit_hash
81 81 result = utils.resolve_ref_or_error(ref, repo)
82 82 _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name)
83 83 assert result == '{}:{}'.format(ref, self.commit_hash)
84 84
85 85 def test_non_supported_refs(self):
86 86 repo = Mock()
87 87 ref = 'bookmark:ref'
88 88 with pytest.raises(JSONRPCError) as excinfo:
89 89 utils.resolve_ref_or_error(ref, repo)
90 90 expected_message = (
91 91 'The specified value:bookmark:`ref` does not exist, or is not allowed.')
92 92 assert excinfo.value.message == expected_message
93 93
94 94 def test_branch_is_not_found(self):
95 95 repo = Mock()
96 96 ref = 'branch:non-existing-one'
97 97 with patch('rhodecode.api.utils._get_ref_hash')\
98 98 as _get_ref_hash:
99 99 _get_ref_hash.side_effect = KeyError()
100 100 with pytest.raises(JSONRPCError) as excinfo:
101 101 utils.resolve_ref_or_error(ref, repo)
102 102 expected_message = (
103 103 'The specified value:branch:`non-existing-one` does not exist, or is not allowed.')
104 104 assert excinfo.value.message == expected_message
105 105
106 106 def test_bookmark_is_not_found(self):
107 107 repo = Mock()
108 108 ref = 'bookmark:non-existing-one'
109 109 with patch('rhodecode.api.utils._get_ref_hash')\
110 110 as _get_ref_hash:
111 111 _get_ref_hash.side_effect = KeyError()
112 112 with pytest.raises(JSONRPCError) as excinfo:
113 113 utils.resolve_ref_or_error(ref, repo)
114 114 expected_message = (
115 115 'The specified value:bookmark:`non-existing-one` does not exist, or is not allowed.')
116 116 assert excinfo.value.message == expected_message
117 117
118 118 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
119 119 def test_ref_cannot_be_parsed(self, ref):
120 120 repo = Mock()
121 121 with pytest.raises(JSONRPCError) as excinfo:
122 122 utils.resolve_ref_or_error(ref, repo)
123 123 expected_message = (
124 124 'Ref `{ref}` given in a wrong format. Please check the API'
125 125 ' documentation for more details'.format(ref=ref)
126 126 )
127 127 assert excinfo.value.message == expected_message
128 128
129 129
130 130 class TestGetRefHash(object):
131 131 def setup(self):
132 132 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
133 133 self.bookmark_name = 'test-bookmark'
134 134
135 135 @pytest.mark.parametrize("alias, branch_name", [
136 136 ("git", "master"),
137 137 ("hg", "default")
138 138 ])
139 139 def test_returns_hash_by_branch_name(self, alias, branch_name):
140 140 with patch('rhodecode.model.db.Repository') as repo:
141 141 repo.scm_instance().alias = alias
142 142 repo.scm_instance().branches = {branch_name: self.commit_hash}
143 143 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
144 144 assert result_hash == self.commit_hash
145 145
146 146 @pytest.mark.parametrize("alias, branch_name", [
147 147 ("git", "master"),
148 148 ("hg", "default")
149 149 ])
150 150 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
151 151 with patch('rhodecode.model.db.Repository') as repo:
152 152 repo.scm_instance().alias = alias
153 153 repo.scm_instance().branches = {}
154 154 with pytest.raises(KeyError):
155 155 utils._get_ref_hash(repo, 'branch', branch_name)
156 156
157 157 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
158 158 with patch('rhodecode.model.db.Repository') as repo:
159 159 repo.scm_instance().alias = 'hg'
160 160 repo.scm_instance().bookmarks = {
161 161 self.bookmark_name: self.commit_hash}
162 162 result_hash = utils._get_ref_hash(
163 163 repo, 'bookmark', self.bookmark_name)
164 164 assert result_hash == self.commit_hash
165 165
166 166 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
167 167 with patch('rhodecode.model.db.Repository') as repo:
168 168 repo.scm_instance().alias = 'hg'
169 169 repo.scm_instance().bookmarks = {}
170 170 with pytest.raises(KeyError):
171 171 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
172 172
173 173 def test_raises_error_when_bookmark_is_specified_for_git(self):
174 174 with patch('rhodecode.model.db.Repository') as repo:
175 175 repo.scm_instance().alias = 'git'
176 176 repo.scm_instance().bookmarks = {
177 177 self.bookmark_name: self.commit_hash}
178 178 with pytest.raises(ValueError):
179 179 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
180 180
181 181
182 182 class TestUserByNameOrError(object):
183 183 def test_user_found_by_id(self):
184 184 fake_user = Mock(id=123)
185 185
186 186 patcher = patch('rhodecode.model.user.UserModel.get_user')
187 187 with patcher as get_user:
188 188 get_user.return_value = fake_user
189 189
190 190 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
191 191 with patcher as get_by_username:
192 192 result = utils.get_user_or_error(123)
193 193 assert result == fake_user
194 194
195 195 def test_user_not_found_by_id_as_str(self):
196 196 fake_user = Mock(id=123)
197 197
198 198 patcher = patch('rhodecode.model.user.UserModel.get_user')
199 199 with patcher as get_user:
200 200 get_user.return_value = fake_user
201 201 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
202 202 with patcher as get_by_username:
203 203 get_by_username.return_value = None
204 204
205 205 with pytest.raises(JSONRPCError):
206 206 utils.get_user_or_error('123')
207 207
208 208 def test_user_found_by_name(self):
209 209 fake_user = Mock(id=123)
210 210
211 211 patcher = patch('rhodecode.model.user.UserModel.get_user')
212 212 with patcher as get_user:
213 213 get_user.return_value = None
214 214
215 215 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
216 216 with patcher as get_by_username:
217 217 get_by_username.return_value = fake_user
218 218
219 219 result = utils.get_user_or_error('test')
220 220 assert result == fake_user
221 221
222 222 def test_user_not_found_by_id(self):
223 223 patcher = patch('rhodecode.model.user.UserModel.get_user')
224 224 with patcher as get_user:
225 225 get_user.return_value = None
226 226 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
227 227 with patcher as get_by_username:
228 228 get_by_username.return_value = None
229 229
230 230 with pytest.raises(JSONRPCError) as excinfo:
231 231 utils.get_user_or_error(123)
232 232
233 233 expected_message = 'user `123` does not exist'
234 234 assert excinfo.value.message == expected_message
235 235
236 236 def test_user_not_found_by_name(self):
237 237 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
238 238 with patcher as get_by_username:
239 239 get_by_username.return_value = None
240 240 with pytest.raises(JSONRPCError) as excinfo:
241 241 utils.get_user_or_error('test')
242 242
243 243 expected_message = 'user `test` does not exist'
244 244 assert excinfo.value.message == expected_message
245 245
246 246
247 247 class TestGetCommitDict(object):
248 248 @pytest.mark.parametrize('filename, expected', [
249 249 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
250 250 (b'sp\xa4cial', u'sp\ufffdcial'),
251 251 ])
252 252 def test_decodes_filenames_to_unicode(self, filename, expected):
253 253 result = utils._get_commit_dict(filename=filename, op='A')
254 254 assert result['filename'] == expected
255 255
256 256
257 257 class TestRepoAccess(object):
258 258 def setup_method(self, method):
259 259
260 260 self.admin_perm_patch = patch(
261 261 'rhodecode.api.utils.HasPermissionAnyApi')
262 262 self.repo_perm_patch = patch(
263 263 'rhodecode.api.utils.HasRepoPermissionAnyApi')
264 264
265 265 def test_has_superadmin_permission_checks_for_admin(self):
266 266 admin_mock = Mock()
267 267 with self.admin_perm_patch as amock:
268 268 amock.return_value = admin_mock
269 269 assert utils.has_superadmin_permission('fake_user')
270 270 amock.assert_called_once_with('hg.admin')
271 271
272 272 admin_mock.assert_called_once_with(user='fake_user')
273 273
274 274 def test_has_repo_permissions_checks_for_repo_access(self):
275 275 repo_mock = Mock()
276 276 fake_repo = Mock()
277 277 with self.repo_perm_patch as rmock:
278 278 rmock.return_value = repo_mock
279 279 assert utils.validate_repo_permissions(
280 280 'fake_user', 'fake_repo_id', fake_repo,
281 281 ['perm1', 'perm2'])
282 282 rmock.assert_called_once_with(*['perm1', 'perm2'])
283 283
284 284 repo_mock.assert_called_once_with(
285 285 user='fake_user', repo_name=fake_repo.repo_name)
286 286
287 287 def test_has_repo_permissions_raises_not_found(self):
288 288 repo_mock = Mock(return_value=False)
289 289 fake_repo = Mock()
290 290 with self.repo_perm_patch as rmock:
291 291 rmock.return_value = repo_mock
292 292 with pytest.raises(JSONRPCError) as excinfo:
293 293 utils.validate_repo_permissions(
294 294 'fake_user', 'fake_repo_id', fake_repo, 'perms')
295 295 assert 'fake_repo_id' in excinfo
@@ -1,106 +1,106 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2019 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 random
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 API_URL = '/_admin/api'
29 29
30 30
31 31 def assert_ok(id_, expected, given):
32 32 expected = jsonify({
33 33 'id': id_,
34 34 'error': None,
35 35 'result': expected
36 36 })
37 37 given = json.loads(given)
38 38 assert expected == given
39 39
40 40
41 41 def assert_error(id_, expected, given):
42 42 expected = jsonify({
43 43 'id': id_,
44 44 'error': expected,
45 45 'result': None
46 46 })
47 47 given = json.loads(given)
48 48 assert expected == given
49 49
50 50
51 51 def jsonify(obj):
52 52 return json.loads(json.dumps(obj))
53 53
54 54
55 55 def build_data(apikey, method, **kw):
56 56 """
57 57 Builds API data with given random ID
58 58
59 59 :param random_id:
60 60 """
61 61 random_id = random.randrange(1, 9999)
62 62 return random_id, json.dumps({
63 63 "id": random_id,
64 64 "api_key": apikey,
65 65 "method": method,
66 66 "args": kw
67 67 })
68 68
69 69
70 70 def api_call(app, params, status=None):
71 71 response = app.post(
72 72 API_URL, content_type='application/json', params=params, status=status)
73 73 return response
74 74
75 75
76 76 def crash(*args, **kwargs):
77 77 raise Exception('Total Crash !')
78 78
79 79
80 80 def expected_permissions(object_with_permissions):
81 81 """
82 82 Returns the expected permissions structure for the given object.
83 83
84 84 The object is expected to be a `Repository`, `RepositoryGroup`,
85 85 or `UserGroup`. They all implement the same permission handling
86 86 API.
87 87 """
88 88 permissions = []
89 89 for _user in object_with_permissions.permissions():
90 90 user_data = {
91 91 'name': _user.username,
92 92 'permission': _user.permission,
93 93 'origin': get_origin(_user),
94 94 'type': "user",
95 95 }
96 96 permissions.append(user_data)
97 97
98 98 for _user_group in object_with_permissions.permission_user_groups():
99 99 user_group_data = {
100 100 'name': _user_group.users_group_name,
101 101 'permission': _user_group.permission,
102 102 'origin': get_origin(_user_group),
103 103 'type': "user_group",
104 104 }
105 105 permissions.append(user_group_data)
106 106 return permissions
@@ -1,449 +1,449 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2014-2018 RhodeCode GmbH
3 # Copyright (C) 2014-2019 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.utils import safe_unicode
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(
174 174 'repository `%s` does not exist' % repoid)
175 175
176 176 return True
177 177
178 178
179 179 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
180 180 """
181 181 Raise JsonRPCError if apiuser is not authorized or return True
182 182
183 183 :param apiuser:
184 184 :param repogroupid: just the id of repository group
185 185 :param repo_group: instance of repo_group
186 186 :param perms:
187 187 """
188 188 if not HasRepoGroupPermissionAnyApi(*perms)(
189 189 user=apiuser, group_name=repo_group.group_name):
190 190 raise JSONRPCError(
191 191 'repository group `%s` does not exist' % repogroupid)
192 192
193 193 return True
194 194
195 195
196 196 def validate_set_owner_permissions(apiuser, owner):
197 197 if isinstance(owner, Optional):
198 198 owner = get_user_or_error(apiuser.user_id)
199 199 else:
200 200 if has_superadmin_permission(apiuser):
201 201 owner = get_user_or_error(owner)
202 202 else:
203 203 # forbid setting owner for non-admins
204 204 raise JSONRPCError(
205 205 'Only RhodeCode super-admin can specify `owner` param')
206 206 return owner
207 207
208 208
209 209 def get_user_or_error(userid):
210 210 """
211 211 Get user by id or name or return JsonRPCError if not found
212 212
213 213 :param userid:
214 214 """
215 215 from rhodecode.model.user import UserModel
216 216 user_model = UserModel()
217 217
218 218 if isinstance(userid, (int, long)):
219 219 try:
220 220 user = user_model.get_user(userid)
221 221 except ValueError:
222 222 user = None
223 223 else:
224 224 user = user_model.get_by_username(userid)
225 225
226 226 if user is None:
227 227 raise JSONRPCError(
228 228 'user `%s` does not exist' % (userid,))
229 229 return user
230 230
231 231
232 232 def get_repo_or_error(repoid):
233 233 """
234 234 Get repo by id or name or return JsonRPCError if not found
235 235
236 236 :param repoid:
237 237 """
238 238 from rhodecode.model.repo import RepoModel
239 239 repo_model = RepoModel()
240 240
241 241 if isinstance(repoid, (int, long)):
242 242 try:
243 243 repo = repo_model.get_repo(repoid)
244 244 except ValueError:
245 245 repo = None
246 246 else:
247 247 repo = repo_model.get_by_repo_name(repoid)
248 248
249 249 if repo is None:
250 250 raise JSONRPCError(
251 251 'repository `%s` does not exist' % (repoid,))
252 252 return repo
253 253
254 254
255 255 def get_repo_group_or_error(repogroupid):
256 256 """
257 257 Get repo group by id or name or return JsonRPCError if not found
258 258
259 259 :param repogroupid:
260 260 """
261 261 from rhodecode.model.repo_group import RepoGroupModel
262 262 repo_group_model = RepoGroupModel()
263 263
264 264 if isinstance(repogroupid, (int, long)):
265 265 try:
266 266 repo_group = repo_group_model._get_repo_group(repogroupid)
267 267 except ValueError:
268 268 repo_group = None
269 269 else:
270 270 repo_group = repo_group_model.get_by_group_name(repogroupid)
271 271
272 272 if repo_group is None:
273 273 raise JSONRPCError(
274 274 'repository group `%s` does not exist' % (repogroupid,))
275 275 return repo_group
276 276
277 277
278 278 def get_user_group_or_error(usergroupid):
279 279 """
280 280 Get user group by id or name or return JsonRPCError if not found
281 281
282 282 :param usergroupid:
283 283 """
284 284 from rhodecode.model.user_group import UserGroupModel
285 285 user_group_model = UserGroupModel()
286 286
287 287 if isinstance(usergroupid, (int, long)):
288 288 try:
289 289 user_group = user_group_model.get_group(usergroupid)
290 290 except ValueError:
291 291 user_group = None
292 292 else:
293 293 user_group = user_group_model.get_by_name(usergroupid)
294 294
295 295 if user_group is None:
296 296 raise JSONRPCError(
297 297 'user group `%s` does not exist' % (usergroupid,))
298 298 return user_group
299 299
300 300
301 301 def get_perm_or_error(permid, prefix=None):
302 302 """
303 303 Get permission by id or name or return JsonRPCError if not found
304 304
305 305 :param permid:
306 306 """
307 307 from rhodecode.model.permission import PermissionModel
308 308
309 309 perm = PermissionModel.cls.get_by_key(permid)
310 310 if perm is None:
311 311 raise JSONRPCError('permission `%s` does not exist' % (permid,))
312 312 if prefix:
313 313 if not perm.permission_name.startswith(prefix):
314 314 raise JSONRPCError('permission `%s` is invalid, '
315 315 'should start with %s' % (permid, prefix))
316 316 return perm
317 317
318 318
319 319 def get_gist_or_error(gistid):
320 320 """
321 321 Get gist by id or gist_access_id or return JsonRPCError if not found
322 322
323 323 :param gistid:
324 324 """
325 325 from rhodecode.model.gist import GistModel
326 326
327 327 gist = GistModel.cls.get_by_access_id(gistid)
328 328 if gist is None:
329 329 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
330 330 return gist
331 331
332 332
333 333 def get_pull_request_or_error(pullrequestid):
334 334 """
335 335 Get pull request by id or return JsonRPCError if not found
336 336
337 337 :param pullrequestid:
338 338 """
339 339 from rhodecode.model.pull_request import PullRequestModel
340 340
341 341 try:
342 342 pull_request = PullRequestModel().get(int(pullrequestid))
343 343 except ValueError:
344 344 raise JSONRPCError('pullrequestid must be an integer')
345 345 if not pull_request:
346 346 raise JSONRPCError('pull request `%s` does not exist' % (
347 347 pullrequestid,))
348 348 return pull_request
349 349
350 350
351 351 def build_commit_data(commit, detail_level):
352 352 parsed_diff = []
353 353 if detail_level == 'extended':
354 354 for f in commit.added:
355 355 parsed_diff.append(_get_commit_dict(filename=f.path, op='A'))
356 356 for f in commit.changed:
357 357 parsed_diff.append(_get_commit_dict(filename=f.path, op='M'))
358 358 for f in commit.removed:
359 359 parsed_diff.append(_get_commit_dict(filename=f.path, op='D'))
360 360
361 361 elif detail_level == 'full':
362 362 from rhodecode.lib.diffs import DiffProcessor
363 363 diff_processor = DiffProcessor(commit.diff())
364 364 for dp in diff_processor.prepare():
365 365 del dp['stats']['ops']
366 366 _stats = dp['stats']
367 367 parsed_diff.append(_get_commit_dict(
368 368 filename=dp['filename'], op=dp['operation'],
369 369 new_revision=dp['new_revision'],
370 370 old_revision=dp['old_revision'],
371 371 raw_diff=dp['raw_diff'], stats=_stats))
372 372
373 373 return parsed_diff
374 374
375 375
376 376 def get_commit_or_error(ref, repo):
377 377 try:
378 378 ref_type, _, ref_hash = ref.split(':')
379 379 except ValueError:
380 380 raise JSONRPCError(
381 381 'Ref `{ref}` given in a wrong format. Please check the API'
382 382 ' documentation for more details'.format(ref=ref))
383 383 try:
384 384 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
385 385 # once get_commit supports ref_types
386 386 return get_commit_from_ref_name(repo, ref_hash)
387 387 except RepositoryError:
388 388 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
389 389
390 390
391 391 def _get_ref_hash(repo, type_, name):
392 392 vcs_repo = repo.scm_instance()
393 393 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
394 394 return vcs_repo.branches[name]
395 395 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
396 396 return vcs_repo.bookmarks[name]
397 397 else:
398 398 raise ValueError()
399 399
400 400
401 401 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
402 402 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
403 403
404 404 def _parse_ref(type_, name, hash_=None):
405 405 return type_, name, hash_
406 406
407 407 try:
408 408 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
409 409 except TypeError:
410 410 raise JSONRPCError(
411 411 'Ref `{ref}` given in a wrong format. Please check the API'
412 412 ' documentation for more details'.format(ref=ref))
413 413
414 414 if ref_type not in allowed_ref_types:
415 415 raise JSONRPCError(
416 416 'Ref `{ref}` type is not allowed. '
417 417 'Only:{allowed_refs} are possible.'.format(
418 418 ref=ref, allowed_refs=allowed_ref_types))
419 419
420 420 try:
421 421 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
422 422 except (KeyError, ValueError):
423 423 raise JSONRPCError(
424 424 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
425 425 type=ref_type, name=ref_name))
426 426
427 427 return ':'.join([ref_type, ref_name, ref_hash])
428 428
429 429
430 430 def _get_commit_dict(
431 431 filename, op, new_revision=None, old_revision=None,
432 432 raw_diff=None, stats=None):
433 433 if stats is None:
434 434 stats = {
435 435 "added": None,
436 436 "binary": None,
437 437 "deleted": None
438 438 }
439 439 return {
440 440 "filename": safe_unicode(filename),
441 441 "op": op,
442 442
443 443 # extra details
444 444 "new_revision": new_revision,
445 445 "old_revision": old_revision,
446 446
447 447 "raw_diff": raw_diff,
448 448 "stats": stats
449 449 }
@@ -1,19 +1,19 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2015-2018 RhodeCode GmbH
3 # Copyright (C) 2015-2019 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 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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,255 +1,255 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 if not has_superadmin_permission(apiuser):
52 52 if gist.gist_owner != apiuser.user_id:
53 53 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
54 54 data = gist.get_api_data()
55 55 if content:
56 56 from rhodecode.model.gist import GistModel
57 57 rev, gist_files = GistModel().get_gist_files(gistid)
58 58 data['content'] = dict([(x.path, x.content) for x in gist_files])
59 59 return data
60 60
61 61
62 62 @jsonrpc_method()
63 63 def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))):
64 64 """
65 65 Get all gists for given user. If userid is empty returned gists
66 66 are for user who called the api
67 67
68 68 :param apiuser: This is filled automatically from the |authtoken|.
69 69 :type apiuser: AuthUser
70 70 :param userid: user to get gists for
71 71 :type userid: Optional(str or int)
72 72 """
73 73
74 74 if not has_superadmin_permission(apiuser):
75 75 # make sure normal user does not pass someone else userid,
76 76 # he is not allowed to do that
77 77 if not isinstance(userid, Optional) and userid != apiuser.user_id:
78 78 raise JSONRPCError(
79 79 'userid is not the same as your user'
80 80 )
81 81
82 82 if isinstance(userid, Optional):
83 83 user_id = apiuser.user_id
84 84 else:
85 85 user_id = get_user_or_error(userid).user_id
86 86
87 87 gists = []
88 88 _gists = Gist().query() \
89 89 .filter(or_(
90 90 Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \
91 91 .filter(Gist.gist_owner == user_id) \
92 92 .order_by(Gist.created_on.desc())
93 93 for gist in _gists:
94 94 gists.append(gist.get_api_data())
95 95 return gists
96 96
97 97
98 98 @jsonrpc_method()
99 99 def create_gist(
100 100 request, apiuser, files, gistid=Optional(None),
101 101 owner=Optional(OAttr('apiuser')),
102 102 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
103 103 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
104 104 description=Optional('')):
105 105 """
106 106 Creates a new Gist.
107 107
108 108 :param apiuser: This is filled automatically from the |authtoken|.
109 109 :type apiuser: AuthUser
110 110 :param files: files to be added to the gist. The data structure has
111 111 to match the following example::
112 112
113 113 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
114 114
115 115 :type files: dict
116 116 :param gistid: Set a custom id for the gist
117 117 :type gistid: Optional(str)
118 118 :param owner: Set the gist owner, defaults to api method caller
119 119 :type owner: Optional(str or int)
120 120 :param gist_type: type of gist ``public`` or ``private``
121 121 :type gist_type: Optional(str)
122 122 :param lifetime: time in minutes of gist lifetime
123 123 :type lifetime: Optional(int)
124 124 :param acl_level: acl level for this gist, can be
125 125 ``acl_public`` or ``acl_private`` If the value is set to
126 126 ``acl_private`` only logged in users are able to access this gist.
127 127 If not set it defaults to ``acl_public``.
128 128 :type acl_level: Optional(str)
129 129 :param description: gist description
130 130 :type description: Optional(str)
131 131
132 132 Example output:
133 133
134 134 .. code-block:: bash
135 135
136 136 id : <id_given_in_input>
137 137 result : {
138 138 "msg": "created new gist",
139 139 "gist": {}
140 140 }
141 141 error : null
142 142
143 143 Example error output:
144 144
145 145 .. code-block:: bash
146 146
147 147 id : <id_given_in_input>
148 148 result : null
149 149 error : {
150 150 "failed to create gist"
151 151 }
152 152
153 153 """
154 154 from rhodecode.model import validation_schema
155 155 from rhodecode.model.validation_schema.schemas import gist_schema
156 156
157 157 if isinstance(owner, Optional):
158 158 owner = apiuser.user_id
159 159
160 160 owner = get_user_or_error(owner)
161 161
162 162 lifetime = Optional.extract(lifetime)
163 163 schema = gist_schema.GistSchema().bind(
164 164 # bind the given values if it's allowed, however the deferred
165 165 # validator will still validate it according to other rules
166 166 lifetime_options=[lifetime])
167 167
168 168 try:
169 169 nodes = gist_schema.nodes_to_sequence(
170 170 files, colander_node=schema.get('nodes'))
171 171
172 172 schema_data = schema.deserialize(dict(
173 173 gistid=Optional.extract(gistid),
174 174 description=Optional.extract(description),
175 175 gist_type=Optional.extract(gist_type),
176 176 lifetime=lifetime,
177 177 gist_acl_level=Optional.extract(acl_level),
178 178 nodes=nodes
179 179 ))
180 180
181 181 # convert to safer format with just KEYs so we sure no duplicates
182 182 schema_data['nodes'] = gist_schema.sequence_to_nodes(
183 183 schema_data['nodes'], colander_node=schema.get('nodes'))
184 184
185 185 except validation_schema.Invalid as err:
186 186 raise JSONRPCValidationError(colander_exc=err)
187 187
188 188 try:
189 189 gist = GistModel().create(
190 190 owner=owner,
191 191 gist_id=schema_data['gistid'],
192 192 description=schema_data['description'],
193 193 gist_mapping=schema_data['nodes'],
194 194 gist_type=schema_data['gist_type'],
195 195 lifetime=schema_data['lifetime'],
196 196 gist_acl_level=schema_data['gist_acl_level'])
197 197 Session().commit()
198 198 return {
199 199 'msg': 'created new gist',
200 200 'gist': gist.get_api_data()
201 201 }
202 202 except Exception:
203 203 log.exception('Error occurred during creation of gist')
204 204 raise JSONRPCError('failed to create gist')
205 205
206 206
207 207 @jsonrpc_method()
208 208 def delete_gist(request, apiuser, gistid):
209 209 """
210 210 Deletes existing gist
211 211
212 212 :param apiuser: filled automatically from apikey
213 213 :type apiuser: AuthUser
214 214 :param gistid: id of gist to delete
215 215 :type gistid: str
216 216
217 217 Example output:
218 218
219 219 .. code-block:: bash
220 220
221 221 id : <id_given_in_input>
222 222 result : {
223 223 "deleted gist ID: <gist_id>",
224 224 "gist": null
225 225 }
226 226 error : null
227 227
228 228 Example error output:
229 229
230 230 .. code-block:: bash
231 231
232 232 id : <id_given_in_input>
233 233 result : null
234 234 error : {
235 235 "failed to delete gist ID:<gist_id>"
236 236 }
237 237
238 238 """
239 239
240 240 gist = get_gist_or_error(gistid)
241 241 if not has_superadmin_permission(apiuser):
242 242 if gist.gist_owner != apiuser.user_id:
243 243 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
244 244
245 245 try:
246 246 GistModel().delete(gist)
247 247 Session().commit()
248 248 return {
249 249 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,),
250 250 'gist': None
251 251 }
252 252 except Exception:
253 253 log.exception('Error occured during gist deletion')
254 254 raise JSONRPCError('failed to delete gist ID:%s'
255 255 % (gist.gist_access_id,)) No newline at end of file
@@ -1,937 +1,937 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2019 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 import events
25 25 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
26 26 from rhodecode.api.utils import (
27 27 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
28 28 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
29 29 validate_repo_permissions, resolve_ref_or_error)
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.model.changeset_status import ChangesetStatusModel
34 34 from rhodecode.model.comment import CommentsModel
35 35 from rhodecode.model.db import Session, ChangesetStatus, ChangesetComment
36 36 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
37 37 from rhodecode.model.settings import SettingsModel
38 38 from rhodecode.model.validation_schema import Invalid
39 39 from rhodecode.model.validation_schema.schemas.reviewer_schema import(
40 40 ReviewerListSchema)
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 @jsonrpc_method()
46 46 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None)):
47 47 """
48 48 Get a pull request based on the given ID.
49 49
50 50 :param apiuser: This is filled automatically from the |authtoken|.
51 51 :type apiuser: AuthUser
52 52 :param repoid: Optional, repository name or repository ID from where
53 53 the pull request was opened.
54 54 :type repoid: str or int
55 55 :param pullrequestid: ID of the requested pull request.
56 56 :type pullrequestid: int
57 57
58 58 Example output:
59 59
60 60 .. code-block:: bash
61 61
62 62 "id": <id_given_in_input>,
63 63 "result":
64 64 {
65 65 "pull_request_id": "<pull_request_id>",
66 66 "url": "<url>",
67 67 "title": "<title>",
68 68 "description": "<description>",
69 69 "status" : "<status>",
70 70 "created_on": "<date_time_created>",
71 71 "updated_on": "<date_time_updated>",
72 72 "commit_ids": [
73 73 ...
74 74 "<commit_id>",
75 75 "<commit_id>",
76 76 ...
77 77 ],
78 78 "review_status": "<review_status>",
79 79 "mergeable": {
80 80 "status": "<bool>",
81 81 "message": "<message>",
82 82 },
83 83 "source": {
84 84 "clone_url": "<clone_url>",
85 85 "repository": "<repository_name>",
86 86 "reference":
87 87 {
88 88 "name": "<name>",
89 89 "type": "<type>",
90 90 "commit_id": "<commit_id>",
91 91 }
92 92 },
93 93 "target": {
94 94 "clone_url": "<clone_url>",
95 95 "repository": "<repository_name>",
96 96 "reference":
97 97 {
98 98 "name": "<name>",
99 99 "type": "<type>",
100 100 "commit_id": "<commit_id>",
101 101 }
102 102 },
103 103 "merge": {
104 104 "clone_url": "<clone_url>",
105 105 "reference":
106 106 {
107 107 "name": "<name>",
108 108 "type": "<type>",
109 109 "commit_id": "<commit_id>",
110 110 }
111 111 },
112 112 "author": <user_obj>,
113 113 "reviewers": [
114 114 ...
115 115 {
116 116 "user": "<user_obj>",
117 117 "review_status": "<review_status>",
118 118 }
119 119 ...
120 120 ]
121 121 },
122 122 "error": null
123 123 """
124 124
125 125 pull_request = get_pull_request_or_error(pullrequestid)
126 126 if Optional.extract(repoid):
127 127 repo = get_repo_or_error(repoid)
128 128 else:
129 129 repo = pull_request.target_repo
130 130
131 131 if not PullRequestModel().check_user_read(
132 132 pull_request, apiuser, api=True):
133 133 raise JSONRPCError('repository `%s` or pull request `%s` '
134 134 'does not exist' % (repoid, pullrequestid))
135 135 data = pull_request.get_api_data()
136 136 return data
137 137
138 138
139 139 @jsonrpc_method()
140 140 def get_pull_requests(request, apiuser, repoid, status=Optional('new')):
141 141 """
142 142 Get all pull requests from the repository specified in `repoid`.
143 143
144 144 :param apiuser: This is filled automatically from the |authtoken|.
145 145 :type apiuser: AuthUser
146 146 :param repoid: Optional repository name or repository ID.
147 147 :type repoid: str or int
148 148 :param status: Only return pull requests with the specified status.
149 149 Valid options are.
150 150 * ``new`` (default)
151 151 * ``open``
152 152 * ``closed``
153 153 :type status: str
154 154
155 155 Example output:
156 156
157 157 .. code-block:: bash
158 158
159 159 "id": <id_given_in_input>,
160 160 "result":
161 161 [
162 162 ...
163 163 {
164 164 "pull_request_id": "<pull_request_id>",
165 165 "url": "<url>",
166 166 "title" : "<title>",
167 167 "description": "<description>",
168 168 "status": "<status>",
169 169 "created_on": "<date_time_created>",
170 170 "updated_on": "<date_time_updated>",
171 171 "commit_ids": [
172 172 ...
173 173 "<commit_id>",
174 174 "<commit_id>",
175 175 ...
176 176 ],
177 177 "review_status": "<review_status>",
178 178 "mergeable": {
179 179 "status": "<bool>",
180 180 "message: "<message>",
181 181 },
182 182 "source": {
183 183 "clone_url": "<clone_url>",
184 184 "reference":
185 185 {
186 186 "name": "<name>",
187 187 "type": "<type>",
188 188 "commit_id": "<commit_id>",
189 189 }
190 190 },
191 191 "target": {
192 192 "clone_url": "<clone_url>",
193 193 "reference":
194 194 {
195 195 "name": "<name>",
196 196 "type": "<type>",
197 197 "commit_id": "<commit_id>",
198 198 }
199 199 },
200 200 "merge": {
201 201 "clone_url": "<clone_url>",
202 202 "reference":
203 203 {
204 204 "name": "<name>",
205 205 "type": "<type>",
206 206 "commit_id": "<commit_id>",
207 207 }
208 208 },
209 209 "author": <user_obj>,
210 210 "reviewers": [
211 211 ...
212 212 {
213 213 "user": "<user_obj>",
214 214 "review_status": "<review_status>",
215 215 }
216 216 ...
217 217 ]
218 218 }
219 219 ...
220 220 ],
221 221 "error": null
222 222
223 223 """
224 224 repo = get_repo_or_error(repoid)
225 225 if not has_superadmin_permission(apiuser):
226 226 _perms = (
227 227 'repository.admin', 'repository.write', 'repository.read',)
228 228 validate_repo_permissions(apiuser, repoid, repo, _perms)
229 229
230 230 status = Optional.extract(status)
231 231 pull_requests = PullRequestModel().get_all(repo, statuses=[status])
232 232 data = [pr.get_api_data() for pr in pull_requests]
233 233 return data
234 234
235 235
236 236 @jsonrpc_method()
237 237 def merge_pull_request(
238 238 request, apiuser, pullrequestid, repoid=Optional(None),
239 239 userid=Optional(OAttr('apiuser'))):
240 240 """
241 241 Merge the pull request specified by `pullrequestid` into its target
242 242 repository.
243 243
244 244 :param apiuser: This is filled automatically from the |authtoken|.
245 245 :type apiuser: AuthUser
246 246 :param repoid: Optional, repository name or repository ID of the
247 247 target repository to which the |pr| is to be merged.
248 248 :type repoid: str or int
249 249 :param pullrequestid: ID of the pull request which shall be merged.
250 250 :type pullrequestid: int
251 251 :param userid: Merge the pull request as this user.
252 252 :type userid: Optional(str or int)
253 253
254 254 Example output:
255 255
256 256 .. code-block:: bash
257 257
258 258 "id": <id_given_in_input>,
259 259 "result": {
260 260 "executed": "<bool>",
261 261 "failure_reason": "<int>",
262 262 "merge_commit_id": "<merge_commit_id>",
263 263 "possible": "<bool>",
264 264 "merge_ref": {
265 265 "commit_id": "<commit_id>",
266 266 "type": "<type>",
267 267 "name": "<name>"
268 268 }
269 269 },
270 270 "error": null
271 271 """
272 272 pull_request = get_pull_request_or_error(pullrequestid)
273 273 if Optional.extract(repoid):
274 274 repo = get_repo_or_error(repoid)
275 275 else:
276 276 repo = pull_request.target_repo
277 277
278 278 if not isinstance(userid, Optional):
279 279 if (has_superadmin_permission(apiuser) or
280 280 HasRepoPermissionAnyApi('repository.admin')(
281 281 user=apiuser, repo_name=repo.repo_name)):
282 282 apiuser = get_user_or_error(userid)
283 283 else:
284 284 raise JSONRPCError('userid is not the same as your user')
285 285
286 286 check = MergeCheck.validate(
287 287 pull_request, auth_user=apiuser, translator=request.translate)
288 288 merge_possible = not check.failed
289 289
290 290 if not merge_possible:
291 291 error_messages = []
292 292 for err_type, error_msg in check.errors:
293 293 error_msg = request.translate(error_msg)
294 294 error_messages.append(error_msg)
295 295
296 296 reasons = ','.join(error_messages)
297 297 raise JSONRPCError(
298 298 'merge not possible for following reasons: {}'.format(reasons))
299 299
300 300 target_repo = pull_request.target_repo
301 301 extras = vcs_operation_context(
302 302 request.environ, repo_name=target_repo.repo_name,
303 303 username=apiuser.username, action='push',
304 304 scm=target_repo.repo_type)
305 305 merge_response = PullRequestModel().merge_repo(
306 306 pull_request, apiuser, extras=extras)
307 307 if merge_response.executed:
308 308 PullRequestModel().close_pull_request(
309 309 pull_request.pull_request_id, apiuser)
310 310
311 311 Session().commit()
312 312
313 313 # In previous versions the merge response directly contained the merge
314 314 # commit id. It is now contained in the merge reference object. To be
315 315 # backwards compatible we have to extract it again.
316 316 merge_response = merge_response.asdict()
317 317 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
318 318
319 319 return merge_response
320 320
321 321
322 322 @jsonrpc_method()
323 323 def get_pull_request_comments(
324 324 request, apiuser, pullrequestid, repoid=Optional(None)):
325 325 """
326 326 Get all comments of pull request specified with the `pullrequestid`
327 327
328 328 :param apiuser: This is filled automatically from the |authtoken|.
329 329 :type apiuser: AuthUser
330 330 :param repoid: Optional repository name or repository ID.
331 331 :type repoid: str or int
332 332 :param pullrequestid: The pull request ID.
333 333 :type pullrequestid: int
334 334
335 335 Example output:
336 336
337 337 .. code-block:: bash
338 338
339 339 id : <id_given_in_input>
340 340 result : [
341 341 {
342 342 "comment_author": {
343 343 "active": true,
344 344 "full_name_or_username": "Tom Gore",
345 345 "username": "admin"
346 346 },
347 347 "comment_created_on": "2017-01-02T18:43:45.533",
348 348 "comment_f_path": null,
349 349 "comment_id": 25,
350 350 "comment_lineno": null,
351 351 "comment_status": {
352 352 "status": "under_review",
353 353 "status_lbl": "Under Review"
354 354 },
355 355 "comment_text": "Example text",
356 356 "comment_type": null,
357 357 "pull_request_version": null
358 358 }
359 359 ],
360 360 error : null
361 361 """
362 362
363 363 pull_request = get_pull_request_or_error(pullrequestid)
364 364 if Optional.extract(repoid):
365 365 repo = get_repo_or_error(repoid)
366 366 else:
367 367 repo = pull_request.target_repo
368 368
369 369 if not PullRequestModel().check_user_read(
370 370 pull_request, apiuser, api=True):
371 371 raise JSONRPCError('repository `%s` or pull request `%s` '
372 372 'does not exist' % (repoid, pullrequestid))
373 373
374 374 (pull_request_latest,
375 375 pull_request_at_ver,
376 376 pull_request_display_obj,
377 377 at_version) = PullRequestModel().get_pr_version(
378 378 pull_request.pull_request_id, version=None)
379 379
380 380 versions = pull_request_display_obj.versions()
381 381 ver_map = {
382 382 ver.pull_request_version_id: cnt
383 383 for cnt, ver in enumerate(versions, 1)
384 384 }
385 385
386 386 # GENERAL COMMENTS with versions #
387 387 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
388 388 q = q.order_by(ChangesetComment.comment_id.asc())
389 389 general_comments = q.all()
390 390
391 391 # INLINE COMMENTS with versions #
392 392 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
393 393 q = q.order_by(ChangesetComment.comment_id.asc())
394 394 inline_comments = q.all()
395 395
396 396 data = []
397 397 for comment in inline_comments + general_comments:
398 398 full_data = comment.get_api_data()
399 399 pr_version_id = None
400 400 if comment.pull_request_version_id:
401 401 pr_version_id = 'v{}'.format(
402 402 ver_map[comment.pull_request_version_id])
403 403
404 404 # sanitize some entries
405 405
406 406 full_data['pull_request_version'] = pr_version_id
407 407 full_data['comment_author'] = {
408 408 'username': full_data['comment_author'].username,
409 409 'full_name_or_username': full_data['comment_author'].full_name_or_username,
410 410 'active': full_data['comment_author'].active,
411 411 }
412 412
413 413 if full_data['comment_status']:
414 414 full_data['comment_status'] = {
415 415 'status': full_data['comment_status'][0].status,
416 416 'status_lbl': full_data['comment_status'][0].status_lbl,
417 417 }
418 418 else:
419 419 full_data['comment_status'] = {}
420 420
421 421 data.append(full_data)
422 422 return data
423 423
424 424
425 425 @jsonrpc_method()
426 426 def comment_pull_request(
427 427 request, apiuser, pullrequestid, repoid=Optional(None),
428 428 message=Optional(None), commit_id=Optional(None), status=Optional(None),
429 429 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
430 430 resolves_comment_id=Optional(None),
431 431 userid=Optional(OAttr('apiuser'))):
432 432 """
433 433 Comment on the pull request specified with the `pullrequestid`,
434 434 in the |repo| specified by the `repoid`, and optionally change the
435 435 review status.
436 436
437 437 :param apiuser: This is filled automatically from the |authtoken|.
438 438 :type apiuser: AuthUser
439 439 :param repoid: Optional repository name or repository ID.
440 440 :type repoid: str or int
441 441 :param pullrequestid: The pull request ID.
442 442 :type pullrequestid: int
443 443 :param commit_id: Specify the commit_id for which to set a comment. If
444 444 given commit_id is different than latest in the PR status
445 445 change won't be performed.
446 446 :type commit_id: str
447 447 :param message: The text content of the comment.
448 448 :type message: str
449 449 :param status: (**Optional**) Set the approval status of the pull
450 450 request. One of: 'not_reviewed', 'approved', 'rejected',
451 451 'under_review'
452 452 :type status: str
453 453 :param comment_type: Comment type, one of: 'note', 'todo'
454 454 :type comment_type: Optional(str), default: 'note'
455 455 :param userid: Comment on the pull request as this user
456 456 :type userid: Optional(str or int)
457 457
458 458 Example output:
459 459
460 460 .. code-block:: bash
461 461
462 462 id : <id_given_in_input>
463 463 result : {
464 464 "pull_request_id": "<Integer>",
465 465 "comment_id": "<Integer>",
466 466 "status": {"given": <given_status>,
467 467 "was_changed": <bool status_was_actually_changed> },
468 468 },
469 469 error : null
470 470 """
471 471 pull_request = get_pull_request_or_error(pullrequestid)
472 472 if Optional.extract(repoid):
473 473 repo = get_repo_or_error(repoid)
474 474 else:
475 475 repo = pull_request.target_repo
476 476
477 477 if not isinstance(userid, Optional):
478 478 if (has_superadmin_permission(apiuser) or
479 479 HasRepoPermissionAnyApi('repository.admin')(
480 480 user=apiuser, repo_name=repo.repo_name)):
481 481 apiuser = get_user_or_error(userid)
482 482 else:
483 483 raise JSONRPCError('userid is not the same as your user')
484 484
485 485 if not PullRequestModel().check_user_read(
486 486 pull_request, apiuser, api=True):
487 487 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
488 488 message = Optional.extract(message)
489 489 status = Optional.extract(status)
490 490 commit_id = Optional.extract(commit_id)
491 491 comment_type = Optional.extract(comment_type)
492 492 resolves_comment_id = Optional.extract(resolves_comment_id)
493 493
494 494 if not message and not status:
495 495 raise JSONRPCError(
496 496 'Both message and status parameters are missing. '
497 497 'At least one is required.')
498 498
499 499 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
500 500 status is not None):
501 501 raise JSONRPCError('Unknown comment status: `%s`' % status)
502 502
503 503 if commit_id and commit_id not in pull_request.revisions:
504 504 raise JSONRPCError(
505 505 'Invalid commit_id `%s` for this pull request.' % commit_id)
506 506
507 507 allowed_to_change_status = PullRequestModel().check_user_change_status(
508 508 pull_request, apiuser)
509 509
510 510 # if commit_id is passed re-validated if user is allowed to change status
511 511 # based on latest commit_id from the PR
512 512 if commit_id:
513 513 commit_idx = pull_request.revisions.index(commit_id)
514 514 if commit_idx != 0:
515 515 allowed_to_change_status = False
516 516
517 517 if resolves_comment_id:
518 518 comment = ChangesetComment.get(resolves_comment_id)
519 519 if not comment:
520 520 raise JSONRPCError(
521 521 'Invalid resolves_comment_id `%s` for this pull request.'
522 522 % resolves_comment_id)
523 523 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
524 524 raise JSONRPCError(
525 525 'Comment `%s` is wrong type for setting status to resolved.'
526 526 % resolves_comment_id)
527 527
528 528 text = message
529 529 status_label = ChangesetStatus.get_status_lbl(status)
530 530 if status and allowed_to_change_status:
531 531 st_message = ('Status change %(transition_icon)s %(status)s'
532 532 % {'transition_icon': '>', 'status': status_label})
533 533 text = message or st_message
534 534
535 535 rc_config = SettingsModel().get_all_settings()
536 536 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
537 537
538 538 status_change = status and allowed_to_change_status
539 539 comment = CommentsModel().create(
540 540 text=text,
541 541 repo=pull_request.target_repo.repo_id,
542 542 user=apiuser.user_id,
543 543 pull_request=pull_request.pull_request_id,
544 544 f_path=None,
545 545 line_no=None,
546 546 status_change=(status_label if status_change else None),
547 547 status_change_type=(status if status_change else None),
548 548 closing_pr=False,
549 549 renderer=renderer,
550 550 comment_type=comment_type,
551 551 resolves_comment_id=resolves_comment_id,
552 552 auth_user=apiuser
553 553 )
554 554
555 555 if allowed_to_change_status and status:
556 556 ChangesetStatusModel().set_status(
557 557 pull_request.target_repo.repo_id,
558 558 status,
559 559 apiuser.user_id,
560 560 comment,
561 561 pull_request=pull_request.pull_request_id
562 562 )
563 563 Session().flush()
564 564
565 565 Session().commit()
566 566 data = {
567 567 'pull_request_id': pull_request.pull_request_id,
568 568 'comment_id': comment.comment_id if comment else None,
569 569 'status': {'given': status, 'was_changed': status_change},
570 570 }
571 571 return data
572 572
573 573
574 574 @jsonrpc_method()
575 575 def create_pull_request(
576 576 request, apiuser, source_repo, target_repo, source_ref, target_ref,
577 577 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
578 578 reviewers=Optional(None)):
579 579 """
580 580 Creates a new pull request.
581 581
582 582 Accepts refs in the following formats:
583 583
584 584 * branch:<branch_name>:<sha>
585 585 * branch:<branch_name>
586 586 * bookmark:<bookmark_name>:<sha> (Mercurial only)
587 587 * bookmark:<bookmark_name> (Mercurial only)
588 588
589 589 :param apiuser: This is filled automatically from the |authtoken|.
590 590 :type apiuser: AuthUser
591 591 :param source_repo: Set the source repository name.
592 592 :type source_repo: str
593 593 :param target_repo: Set the target repository name.
594 594 :type target_repo: str
595 595 :param source_ref: Set the source ref name.
596 596 :type source_ref: str
597 597 :param target_ref: Set the target ref name.
598 598 :type target_ref: str
599 599 :param title: Optionally Set the pull request title, it's generated otherwise
600 600 :type title: str
601 601 :param description: Set the pull request description.
602 602 :type description: Optional(str)
603 603 :type description_renderer: Optional(str)
604 604 :param description_renderer: Set pull request renderer for the description.
605 605 It should be 'rst', 'markdown' or 'plain'. If not give default
606 606 system renderer will be used
607 607 :param reviewers: Set the new pull request reviewers list.
608 608 Reviewer defined by review rules will be added automatically to the
609 609 defined list.
610 610 :type reviewers: Optional(list)
611 611 Accepts username strings or objects of the format:
612 612
613 613 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
614 614 """
615 615
616 616 source_db_repo = get_repo_or_error(source_repo)
617 617 target_db_repo = get_repo_or_error(target_repo)
618 618 if not has_superadmin_permission(apiuser):
619 619 _perms = ('repository.admin', 'repository.write', 'repository.read',)
620 620 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
621 621
622 622 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
623 623 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
624 624
625 625 source_scm = source_db_repo.scm_instance()
626 626 target_scm = target_db_repo.scm_instance()
627 627
628 628 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
629 629 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
630 630
631 631 ancestor = source_scm.get_common_ancestor(
632 632 source_commit.raw_id, target_commit.raw_id, target_scm)
633 633 if not ancestor:
634 634 raise JSONRPCError('no common ancestor found')
635 635
636 636 # recalculate target ref based on ancestor
637 637 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
638 638 full_target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
639 639
640 640 commit_ranges = target_scm.compare(
641 641 target_commit.raw_id, source_commit.raw_id, source_scm,
642 642 merge=True, pre_load=[])
643 643
644 644 if not commit_ranges:
645 645 raise JSONRPCError('no commits found')
646 646
647 647 reviewer_objects = Optional.extract(reviewers) or []
648 648
649 649 # serialize and validate passed in given reviewers
650 650 if reviewer_objects:
651 651 schema = ReviewerListSchema()
652 652 try:
653 653 reviewer_objects = schema.deserialize(reviewer_objects)
654 654 except Invalid as err:
655 655 raise JSONRPCValidationError(colander_exc=err)
656 656
657 657 # validate users
658 658 for reviewer_object in reviewer_objects:
659 659 user = get_user_or_error(reviewer_object['username'])
660 660 reviewer_object['user_id'] = user.user_id
661 661
662 662 get_default_reviewers_data, validate_default_reviewers = \
663 663 PullRequestModel().get_reviewer_functions()
664 664
665 665 # recalculate reviewers logic, to make sure we can validate this
666 666 reviewer_rules = get_default_reviewers_data(
667 667 apiuser.get_instance(), source_db_repo,
668 668 source_commit, target_db_repo, target_commit)
669 669
670 670 # now MERGE our given with the calculated
671 671 reviewer_objects = reviewer_rules['reviewers'] + reviewer_objects
672 672
673 673 try:
674 674 reviewers = validate_default_reviewers(
675 675 reviewer_objects, reviewer_rules)
676 676 except ValueError as e:
677 677 raise JSONRPCError('Reviewers Validation: {}'.format(e))
678 678
679 679 title = Optional.extract(title)
680 680 if not title:
681 681 title_source_ref = source_ref.split(':', 2)[1]
682 682 title = PullRequestModel().generate_pullrequest_title(
683 683 source=source_repo,
684 684 source_ref=title_source_ref,
685 685 target=target_repo
686 686 )
687 687 # fetch renderer, if set fallback to plain in case of PR
688 688 rc_config = SettingsModel().get_all_settings()
689 689 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
690 690 description = Optional.extract(description)
691 691 description_renderer = Optional.extract(description_renderer) or default_system_renderer
692 692
693 693 pull_request = PullRequestModel().create(
694 694 created_by=apiuser.user_id,
695 695 source_repo=source_repo,
696 696 source_ref=full_source_ref,
697 697 target_repo=target_repo,
698 698 target_ref=full_target_ref,
699 699 revisions=[commit.raw_id for commit in reversed(commit_ranges)],
700 700 reviewers=reviewers,
701 701 title=title,
702 702 description=description,
703 703 description_renderer=description_renderer,
704 704 reviewer_data=reviewer_rules,
705 705 auth_user=apiuser
706 706 )
707 707
708 708 Session().commit()
709 709 data = {
710 710 'msg': 'Created new pull request `{}`'.format(title),
711 711 'pull_request_id': pull_request.pull_request_id,
712 712 }
713 713 return data
714 714
715 715
716 716 @jsonrpc_method()
717 717 def update_pull_request(
718 718 request, apiuser, pullrequestid, repoid=Optional(None),
719 719 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
720 720 reviewers=Optional(None), update_commits=Optional(None)):
721 721 """
722 722 Updates a pull request.
723 723
724 724 :param apiuser: This is filled automatically from the |authtoken|.
725 725 :type apiuser: AuthUser
726 726 :param repoid: Optional repository name or repository ID.
727 727 :type repoid: str or int
728 728 :param pullrequestid: The pull request ID.
729 729 :type pullrequestid: int
730 730 :param title: Set the pull request title.
731 731 :type title: str
732 732 :param description: Update pull request description.
733 733 :type description: Optional(str)
734 734 :type description_renderer: Optional(str)
735 735 :param description_renderer: Update pull request renderer for the description.
736 736 It should be 'rst', 'markdown' or 'plain'
737 737 :param reviewers: Update pull request reviewers list with new value.
738 738 :type reviewers: Optional(list)
739 739 Accepts username strings or objects of the format:
740 740
741 741 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
742 742
743 743 :param update_commits: Trigger update of commits for this pull request
744 744 :type: update_commits: Optional(bool)
745 745
746 746 Example output:
747 747
748 748 .. code-block:: bash
749 749
750 750 id : <id_given_in_input>
751 751 result : {
752 752 "msg": "Updated pull request `63`",
753 753 "pull_request": <pull_request_object>,
754 754 "updated_reviewers": {
755 755 "added": [
756 756 "username"
757 757 ],
758 758 "removed": []
759 759 },
760 760 "updated_commits": {
761 761 "added": [
762 762 "<sha1_hash>"
763 763 ],
764 764 "common": [
765 765 "<sha1_hash>",
766 766 "<sha1_hash>",
767 767 ],
768 768 "removed": []
769 769 }
770 770 }
771 771 error : null
772 772 """
773 773
774 774 pull_request = get_pull_request_or_error(pullrequestid)
775 775 if Optional.extract(repoid):
776 776 repo = get_repo_or_error(repoid)
777 777 else:
778 778 repo = pull_request.target_repo
779 779
780 780 if not PullRequestModel().check_user_update(
781 781 pull_request, apiuser, api=True):
782 782 raise JSONRPCError(
783 783 'pull request `%s` update failed, no permission to update.' % (
784 784 pullrequestid,))
785 785 if pull_request.is_closed():
786 786 raise JSONRPCError(
787 787 'pull request `%s` update failed, pull request is closed' % (
788 788 pullrequestid,))
789 789
790 790 reviewer_objects = Optional.extract(reviewers) or []
791 791
792 792 if reviewer_objects:
793 793 schema = ReviewerListSchema()
794 794 try:
795 795 reviewer_objects = schema.deserialize(reviewer_objects)
796 796 except Invalid as err:
797 797 raise JSONRPCValidationError(colander_exc=err)
798 798
799 799 # validate users
800 800 for reviewer_object in reviewer_objects:
801 801 user = get_user_or_error(reviewer_object['username'])
802 802 reviewer_object['user_id'] = user.user_id
803 803
804 804 get_default_reviewers_data, get_validated_reviewers = \
805 805 PullRequestModel().get_reviewer_functions()
806 806
807 807 # re-use stored rules
808 808 reviewer_rules = pull_request.reviewer_data
809 809 try:
810 810 reviewers = get_validated_reviewers(
811 811 reviewer_objects, reviewer_rules)
812 812 except ValueError as e:
813 813 raise JSONRPCError('Reviewers Validation: {}'.format(e))
814 814 else:
815 815 reviewers = []
816 816
817 817 title = Optional.extract(title)
818 818 description = Optional.extract(description)
819 819 description_renderer = Optional.extract(description_renderer)
820 820
821 821 if title or description:
822 822 PullRequestModel().edit(
823 823 pull_request,
824 824 title or pull_request.title,
825 825 description or pull_request.description,
826 826 description_renderer or pull_request.description_renderer,
827 827 apiuser)
828 828 Session().commit()
829 829
830 830 commit_changes = {"added": [], "common": [], "removed": []}
831 831 if str2bool(Optional.extract(update_commits)):
832 832 if PullRequestModel().has_valid_update_type(pull_request):
833 833 update_response = PullRequestModel().update_commits(
834 834 pull_request)
835 835 commit_changes = update_response.changes or commit_changes
836 836 Session().commit()
837 837
838 838 reviewers_changes = {"added": [], "removed": []}
839 839 if reviewers:
840 840 added_reviewers, removed_reviewers = \
841 841 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
842 842
843 843 reviewers_changes['added'] = sorted(
844 844 [get_user_or_error(n).username for n in added_reviewers])
845 845 reviewers_changes['removed'] = sorted(
846 846 [get_user_or_error(n).username for n in removed_reviewers])
847 847 Session().commit()
848 848
849 849 data = {
850 850 'msg': 'Updated pull request `{}`'.format(
851 851 pull_request.pull_request_id),
852 852 'pull_request': pull_request.get_api_data(),
853 853 'updated_commits': commit_changes,
854 854 'updated_reviewers': reviewers_changes
855 855 }
856 856
857 857 return data
858 858
859 859
860 860 @jsonrpc_method()
861 861 def close_pull_request(
862 862 request, apiuser, pullrequestid, repoid=Optional(None),
863 863 userid=Optional(OAttr('apiuser')), message=Optional('')):
864 864 """
865 865 Close the pull request specified by `pullrequestid`.
866 866
867 867 :param apiuser: This is filled automatically from the |authtoken|.
868 868 :type apiuser: AuthUser
869 869 :param repoid: Repository name or repository ID to which the pull
870 870 request belongs.
871 871 :type repoid: str or int
872 872 :param pullrequestid: ID of the pull request to be closed.
873 873 :type pullrequestid: int
874 874 :param userid: Close the pull request as this user.
875 875 :type userid: Optional(str or int)
876 876 :param message: Optional message to close the Pull Request with. If not
877 877 specified it will be generated automatically.
878 878 :type message: Optional(str)
879 879
880 880 Example output:
881 881
882 882 .. code-block:: bash
883 883
884 884 "id": <id_given_in_input>,
885 885 "result": {
886 886 "pull_request_id": "<int>",
887 887 "close_status": "<str:status_lbl>,
888 888 "closed": "<bool>"
889 889 },
890 890 "error": null
891 891
892 892 """
893 893 _ = request.translate
894 894
895 895 pull_request = get_pull_request_or_error(pullrequestid)
896 896 if Optional.extract(repoid):
897 897 repo = get_repo_or_error(repoid)
898 898 else:
899 899 repo = pull_request.target_repo
900 900
901 901 if not isinstance(userid, Optional):
902 902 if (has_superadmin_permission(apiuser) or
903 903 HasRepoPermissionAnyApi('repository.admin')(
904 904 user=apiuser, repo_name=repo.repo_name)):
905 905 apiuser = get_user_or_error(userid)
906 906 else:
907 907 raise JSONRPCError('userid is not the same as your user')
908 908
909 909 if pull_request.is_closed():
910 910 raise JSONRPCError(
911 911 'pull request `%s` is already closed' % (pullrequestid,))
912 912
913 913 # only owner or admin or person with write permissions
914 914 allowed_to_close = PullRequestModel().check_user_update(
915 915 pull_request, apiuser, api=True)
916 916
917 917 if not allowed_to_close:
918 918 raise JSONRPCError(
919 919 'pull request `%s` close failed, no permission to close.' % (
920 920 pullrequestid,))
921 921
922 922 # message we're using to close the PR, else it's automatically generated
923 923 message = Optional.extract(message)
924 924
925 925 # finally close the PR, with proper message comment
926 926 comment, status = PullRequestModel().close_pull_request_with_comment(
927 927 pull_request, apiuser, repo, message=message, auth_user=apiuser)
928 928 status_lbl = ChangesetStatus.get_status_lbl(status)
929 929
930 930 Session().commit()
931 931
932 932 data = {
933 933 'pull_request_id': pull_request.pull_request_id,
934 934 'close_status': status_lbl,
935 935 'closed': True,
936 936 }
937 937 return data
@@ -1,2099 +1,2099 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2018 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import logging
22 22 import time
23 23
24 24 import rhodecode
25 25 from rhodecode.api import (
26 26 jsonrpc_method, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
27 27 from rhodecode.api.utils import (
28 28 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
29 29 get_user_group_or_error, get_user_or_error, validate_repo_permissions,
30 30 get_perm_or_error, parse_args, get_origin, build_commit_data,
31 31 validate_set_owner_permissions)
32 32 from rhodecode.lib import audit_logger
33 33 from rhodecode.lib import repo_maintenance
34 34 from rhodecode.lib.auth import HasPermissionAnyApi, HasUserGroupPermissionAnyApi
35 35 from rhodecode.lib.celerylib.utils import get_task_id
36 36 from rhodecode.lib.utils2 import str2bool, time_to_datetime, safe_str
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
39 39 from rhodecode.model.changeset_status import ChangesetStatusModel
40 40 from rhodecode.model.comment import CommentsModel
41 41 from rhodecode.model.db import (
42 42 Session, ChangesetStatus, RepositoryField, Repository, RepoGroup,
43 43 ChangesetComment)
44 44 from rhodecode.model.repo import RepoModel
45 45 from rhodecode.model.scm import ScmModel, RepoList
46 46 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
47 47 from rhodecode.model import validation_schema
48 48 from rhodecode.model.validation_schema.schemas import repo_schema
49 49
50 50 log = logging.getLogger(__name__)
51 51
52 52
53 53 @jsonrpc_method()
54 54 def get_repo(request, apiuser, repoid, cache=Optional(True)):
55 55 """
56 56 Gets an existing repository by its name or repository_id.
57 57
58 58 The members section so the output returns users groups or users
59 59 associated with that repository.
60 60
61 61 This command can only be run using an |authtoken| with admin rights,
62 62 or users with at least read rights to the |repo|.
63 63
64 64 :param apiuser: This is filled automatically from the |authtoken|.
65 65 :type apiuser: AuthUser
66 66 :param repoid: The repository name or repository id.
67 67 :type repoid: str or int
68 68 :param cache: use the cached value for last changeset
69 69 :type: cache: Optional(bool)
70 70
71 71 Example output:
72 72
73 73 .. code-block:: bash
74 74
75 75 {
76 76 "error": null,
77 77 "id": <repo_id>,
78 78 "result": {
79 79 "clone_uri": null,
80 80 "created_on": "timestamp",
81 81 "description": "repo description",
82 82 "enable_downloads": false,
83 83 "enable_locking": false,
84 84 "enable_statistics": false,
85 85 "followers": [
86 86 {
87 87 "active": true,
88 88 "admin": false,
89 89 "api_key": "****************************************",
90 90 "api_keys": [
91 91 "****************************************"
92 92 ],
93 93 "email": "user@example.com",
94 94 "emails": [
95 95 "user@example.com"
96 96 ],
97 97 "extern_name": "rhodecode",
98 98 "extern_type": "rhodecode",
99 99 "firstname": "username",
100 100 "ip_addresses": [],
101 101 "language": null,
102 102 "last_login": "2015-09-16T17:16:35.854",
103 103 "lastname": "surname",
104 104 "user_id": <user_id>,
105 105 "username": "name"
106 106 }
107 107 ],
108 108 "fork_of": "parent-repo",
109 109 "landing_rev": [
110 110 "rev",
111 111 "tip"
112 112 ],
113 113 "last_changeset": {
114 114 "author": "User <user@example.com>",
115 115 "branch": "default",
116 116 "date": "timestamp",
117 117 "message": "last commit message",
118 118 "parents": [
119 119 {
120 120 "raw_id": "commit-id"
121 121 }
122 122 ],
123 123 "raw_id": "commit-id",
124 124 "revision": <revision number>,
125 125 "short_id": "short id"
126 126 },
127 127 "lock_reason": null,
128 128 "locked_by": null,
129 129 "locked_date": null,
130 130 "owner": "owner-name",
131 131 "permissions": [
132 132 {
133 133 "name": "super-admin-name",
134 134 "origin": "super-admin",
135 135 "permission": "repository.admin",
136 136 "type": "user"
137 137 },
138 138 {
139 139 "name": "owner-name",
140 140 "origin": "owner",
141 141 "permission": "repository.admin",
142 142 "type": "user"
143 143 },
144 144 {
145 145 "name": "user-group-name",
146 146 "origin": "permission",
147 147 "permission": "repository.write",
148 148 "type": "user_group"
149 149 }
150 150 ],
151 151 "private": true,
152 152 "repo_id": 676,
153 153 "repo_name": "user-group/repo-name",
154 154 "repo_type": "hg"
155 155 }
156 156 }
157 157 """
158 158
159 159 repo = get_repo_or_error(repoid)
160 160 cache = Optional.extract(cache)
161 161
162 162 include_secrets = False
163 163 if has_superadmin_permission(apiuser):
164 164 include_secrets = True
165 165 else:
166 166 # check if we have at least read permission for this repo !
167 167 _perms = (
168 168 'repository.admin', 'repository.write', 'repository.read',)
169 169 validate_repo_permissions(apiuser, repoid, repo, _perms)
170 170
171 171 permissions = []
172 172 for _user in repo.permissions():
173 173 user_data = {
174 174 'name': _user.username,
175 175 'permission': _user.permission,
176 176 'origin': get_origin(_user),
177 177 'type': "user",
178 178 }
179 179 permissions.append(user_data)
180 180
181 181 for _user_group in repo.permission_user_groups():
182 182 user_group_data = {
183 183 'name': _user_group.users_group_name,
184 184 'permission': _user_group.permission,
185 185 'origin': get_origin(_user_group),
186 186 'type': "user_group",
187 187 }
188 188 permissions.append(user_group_data)
189 189
190 190 following_users = [
191 191 user.user.get_api_data(include_secrets=include_secrets)
192 192 for user in repo.followers]
193 193
194 194 if not cache:
195 195 repo.update_commit_cache()
196 196 data = repo.get_api_data(include_secrets=include_secrets)
197 197 data['permissions'] = permissions
198 198 data['followers'] = following_users
199 199 return data
200 200
201 201
202 202 @jsonrpc_method()
203 203 def get_repos(request, apiuser, root=Optional(None), traverse=Optional(True)):
204 204 """
205 205 Lists all existing repositories.
206 206
207 207 This command can only be run using an |authtoken| with admin rights,
208 208 or users with at least read rights to |repos|.
209 209
210 210 :param apiuser: This is filled automatically from the |authtoken|.
211 211 :type apiuser: AuthUser
212 212 :param root: specify root repository group to fetch repositories.
213 213 filters the returned repositories to be members of given root group.
214 214 :type root: Optional(None)
215 215 :param traverse: traverse given root into subrepositories. With this flag
216 216 set to False, it will only return top-level repositories from `root`.
217 217 if root is empty it will return just top-level repositories.
218 218 :type traverse: Optional(True)
219 219
220 220
221 221 Example output:
222 222
223 223 .. code-block:: bash
224 224
225 225 id : <id_given_in_input>
226 226 result: [
227 227 {
228 228 "repo_id" : "<repo_id>",
229 229 "repo_name" : "<reponame>"
230 230 "repo_type" : "<repo_type>",
231 231 "clone_uri" : "<clone_uri>",
232 232 "private": : "<bool>",
233 233 "created_on" : "<datetimecreated>",
234 234 "description" : "<description>",
235 235 "landing_rev": "<landing_rev>",
236 236 "owner": "<repo_owner>",
237 237 "fork_of": "<name_of_fork_parent>",
238 238 "enable_downloads": "<bool>",
239 239 "enable_locking": "<bool>",
240 240 "enable_statistics": "<bool>",
241 241 },
242 242 ...
243 243 ]
244 244 error: null
245 245 """
246 246
247 247 include_secrets = has_superadmin_permission(apiuser)
248 248 _perms = ('repository.read', 'repository.write', 'repository.admin',)
249 249 extras = {'user': apiuser}
250 250
251 251 root = Optional.extract(root)
252 252 traverse = Optional.extract(traverse, binary=True)
253 253
254 254 if root:
255 255 # verify parent existance, if it's empty return an error
256 256 parent = RepoGroup.get_by_group_name(root)
257 257 if not parent:
258 258 raise JSONRPCError(
259 259 'Root repository group `{}` does not exist'.format(root))
260 260
261 261 if traverse:
262 262 repos = RepoModel().get_repos_for_root(root=root, traverse=traverse)
263 263 else:
264 264 repos = RepoModel().get_repos_for_root(root=parent)
265 265 else:
266 266 if traverse:
267 267 repos = RepoModel().get_all()
268 268 else:
269 269 # return just top-level
270 270 repos = RepoModel().get_repos_for_root(root=None)
271 271
272 272 repo_list = RepoList(repos, perm_set=_perms, extra_kwargs=extras)
273 273 return [repo.get_api_data(include_secrets=include_secrets)
274 274 for repo in repo_list]
275 275
276 276
277 277 @jsonrpc_method()
278 278 def get_repo_changeset(request, apiuser, repoid, revision,
279 279 details=Optional('basic')):
280 280 """
281 281 Returns information about a changeset.
282 282
283 283 Additionally parameters define the amount of details returned by
284 284 this function.
285 285
286 286 This command can only be run using an |authtoken| with admin rights,
287 287 or users with at least read rights to the |repo|.
288 288
289 289 :param apiuser: This is filled automatically from the |authtoken|.
290 290 :type apiuser: AuthUser
291 291 :param repoid: The repository name or repository id
292 292 :type repoid: str or int
293 293 :param revision: revision for which listing should be done
294 294 :type revision: str
295 295 :param details: details can be 'basic|extended|full' full gives diff
296 296 info details like the diff itself, and number of changed files etc.
297 297 :type details: Optional(str)
298 298
299 299 """
300 300 repo = get_repo_or_error(repoid)
301 301 if not has_superadmin_permission(apiuser):
302 302 _perms = (
303 303 'repository.admin', 'repository.write', 'repository.read',)
304 304 validate_repo_permissions(apiuser, repoid, repo, _perms)
305 305
306 306 changes_details = Optional.extract(details)
307 307 _changes_details_types = ['basic', 'extended', 'full']
308 308 if changes_details not in _changes_details_types:
309 309 raise JSONRPCError(
310 310 'ret_type must be one of %s' % (
311 311 ','.join(_changes_details_types)))
312 312
313 313 pre_load = ['author', 'branch', 'date', 'message', 'parents',
314 314 'status', '_commit', '_file_paths']
315 315
316 316 try:
317 317 cs = repo.get_commit(commit_id=revision, pre_load=pre_load)
318 318 except TypeError as e:
319 319 raise JSONRPCError(safe_str(e))
320 320 _cs_json = cs.__json__()
321 321 _cs_json['diff'] = build_commit_data(cs, changes_details)
322 322 if changes_details == 'full':
323 323 _cs_json['refs'] = cs._get_refs()
324 324 return _cs_json
325 325
326 326
327 327 @jsonrpc_method()
328 328 def get_repo_changesets(request, apiuser, repoid, start_rev, limit,
329 329 details=Optional('basic')):
330 330 """
331 331 Returns a set of commits limited by the number starting
332 332 from the `start_rev` option.
333 333
334 334 Additional parameters define the amount of details returned by this
335 335 function.
336 336
337 337 This command can only be run using an |authtoken| with admin rights,
338 338 or users with at least read rights to |repos|.
339 339
340 340 :param apiuser: This is filled automatically from the |authtoken|.
341 341 :type apiuser: AuthUser
342 342 :param repoid: The repository name or repository ID.
343 343 :type repoid: str or int
344 344 :param start_rev: The starting revision from where to get changesets.
345 345 :type start_rev: str
346 346 :param limit: Limit the number of commits to this amount
347 347 :type limit: str or int
348 348 :param details: Set the level of detail returned. Valid option are:
349 349 ``basic``, ``extended`` and ``full``.
350 350 :type details: Optional(str)
351 351
352 352 .. note::
353 353
354 354 Setting the parameter `details` to the value ``full`` is extensive
355 355 and returns details like the diff itself, and the number
356 356 of changed files.
357 357
358 358 """
359 359 repo = get_repo_or_error(repoid)
360 360 if not has_superadmin_permission(apiuser):
361 361 _perms = (
362 362 'repository.admin', 'repository.write', 'repository.read',)
363 363 validate_repo_permissions(apiuser, repoid, repo, _perms)
364 364
365 365 changes_details = Optional.extract(details)
366 366 _changes_details_types = ['basic', 'extended', 'full']
367 367 if changes_details not in _changes_details_types:
368 368 raise JSONRPCError(
369 369 'ret_type must be one of %s' % (
370 370 ','.join(_changes_details_types)))
371 371
372 372 limit = int(limit)
373 373 pre_load = ['author', 'branch', 'date', 'message', 'parents',
374 374 'status', '_commit', '_file_paths']
375 375
376 376 vcs_repo = repo.scm_instance()
377 377 # SVN needs a special case to distinguish its index and commit id
378 378 if vcs_repo and vcs_repo.alias == 'svn' and (start_rev == '0'):
379 379 start_rev = vcs_repo.commit_ids[0]
380 380
381 381 try:
382 382 commits = vcs_repo.get_commits(
383 383 start_id=start_rev, pre_load=pre_load)
384 384 except TypeError as e:
385 385 raise JSONRPCError(safe_str(e))
386 386 except Exception:
387 387 log.exception('Fetching of commits failed')
388 388 raise JSONRPCError('Error occurred during commit fetching')
389 389
390 390 ret = []
391 391 for cnt, commit in enumerate(commits):
392 392 if cnt >= limit != -1:
393 393 break
394 394 _cs_json = commit.__json__()
395 395 _cs_json['diff'] = build_commit_data(commit, changes_details)
396 396 if changes_details == 'full':
397 397 _cs_json['refs'] = {
398 398 'branches': [commit.branch],
399 399 'bookmarks': getattr(commit, 'bookmarks', []),
400 400 'tags': commit.tags
401 401 }
402 402 ret.append(_cs_json)
403 403 return ret
404 404
405 405
406 406 @jsonrpc_method()
407 407 def get_repo_nodes(request, apiuser, repoid, revision, root_path,
408 408 ret_type=Optional('all'), details=Optional('basic'),
409 409 max_file_bytes=Optional(None)):
410 410 """
411 411 Returns a list of nodes and children in a flat list for a given
412 412 path at given revision.
413 413
414 414 It's possible to specify ret_type to show only `files` or `dirs`.
415 415
416 416 This command can only be run using an |authtoken| with admin rights,
417 417 or users with at least read rights to |repos|.
418 418
419 419 :param apiuser: This is filled automatically from the |authtoken|.
420 420 :type apiuser: AuthUser
421 421 :param repoid: The repository name or repository ID.
422 422 :type repoid: str or int
423 423 :param revision: The revision for which listing should be done.
424 424 :type revision: str
425 425 :param root_path: The path from which to start displaying.
426 426 :type root_path: str
427 427 :param ret_type: Set the return type. Valid options are
428 428 ``all`` (default), ``files`` and ``dirs``.
429 429 :type ret_type: Optional(str)
430 430 :param details: Returns extended information about nodes, such as
431 431 md5, binary, and or content. The valid options are ``basic`` and
432 432 ``full``.
433 433 :type details: Optional(str)
434 434 :param max_file_bytes: Only return file content under this file size bytes
435 435 :type details: Optional(int)
436 436
437 437 Example output:
438 438
439 439 .. code-block:: bash
440 440
441 441 id : <id_given_in_input>
442 442 result: [
443 443 {
444 444 "name" : "<name>"
445 445 "type" : "<type>",
446 446 "binary": "<true|false>" (only in extended mode)
447 447 "md5" : "<md5 of file content>" (only in extended mode)
448 448 },
449 449 ...
450 450 ]
451 451 error: null
452 452 """
453 453
454 454 repo = get_repo_or_error(repoid)
455 455 if not has_superadmin_permission(apiuser):
456 456 _perms = (
457 457 'repository.admin', 'repository.write', 'repository.read',)
458 458 validate_repo_permissions(apiuser, repoid, repo, _perms)
459 459
460 460 ret_type = Optional.extract(ret_type)
461 461 details = Optional.extract(details)
462 462 _extended_types = ['basic', 'full']
463 463 if details not in _extended_types:
464 464 raise JSONRPCError(
465 465 'ret_type must be one of %s' % (','.join(_extended_types)))
466 466 extended_info = False
467 467 content = False
468 468 if details == 'basic':
469 469 extended_info = True
470 470
471 471 if details == 'full':
472 472 extended_info = content = True
473 473
474 474 _map = {}
475 475 try:
476 476 # check if repo is not empty by any chance, skip quicker if it is.
477 477 _scm = repo.scm_instance()
478 478 if _scm.is_empty():
479 479 return []
480 480
481 481 _d, _f = ScmModel().get_nodes(
482 482 repo, revision, root_path, flat=False,
483 483 extended_info=extended_info, content=content,
484 484 max_file_bytes=max_file_bytes)
485 485 _map = {
486 486 'all': _d + _f,
487 487 'files': _f,
488 488 'dirs': _d,
489 489 }
490 490 return _map[ret_type]
491 491 except KeyError:
492 492 raise JSONRPCError(
493 493 'ret_type must be one of %s' % (','.join(sorted(_map.keys()))))
494 494 except Exception:
495 495 log.exception("Exception occurred while trying to get repo nodes")
496 496 raise JSONRPCError(
497 497 'failed to get repo: `%s` nodes' % repo.repo_name
498 498 )
499 499
500 500
501 501 @jsonrpc_method()
502 502 def get_repo_refs(request, apiuser, repoid):
503 503 """
504 504 Returns a dictionary of current references. It returns
505 505 bookmarks, branches, closed_branches, and tags for given repository
506 506
507 507 It's possible to specify ret_type to show only `files` or `dirs`.
508 508
509 509 This command can only be run using an |authtoken| with admin rights,
510 510 or users with at least read rights to |repos|.
511 511
512 512 :param apiuser: This is filled automatically from the |authtoken|.
513 513 :type apiuser: AuthUser
514 514 :param repoid: The repository name or repository ID.
515 515 :type repoid: str or int
516 516
517 517 Example output:
518 518
519 519 .. code-block:: bash
520 520
521 521 id : <id_given_in_input>
522 522 "result": {
523 523 "bookmarks": {
524 524 "dev": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
525 525 "master": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
526 526 },
527 527 "branches": {
528 528 "default": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
529 529 "stable": "367f590445081d8ec8c2ea0456e73ae1f1c3d6cf"
530 530 },
531 531 "branches_closed": {},
532 532 "tags": {
533 533 "tip": "5611d30200f4040ba2ab4f3d64e5b06408a02188",
534 534 "v4.4.0": "1232313f9e6adac5ce5399c2a891dc1e72b79022",
535 535 "v4.4.1": "cbb9f1d329ae5768379cdec55a62ebdd546c4e27",
536 536 "v4.4.2": "24ffe44a27fcd1c5b6936144e176b9f6dd2f3a17",
537 537 }
538 538 }
539 539 error: null
540 540 """
541 541
542 542 repo = get_repo_or_error(repoid)
543 543 if not has_superadmin_permission(apiuser):
544 544 _perms = ('repository.admin', 'repository.write', 'repository.read',)
545 545 validate_repo_permissions(apiuser, repoid, repo, _perms)
546 546
547 547 try:
548 548 # check if repo is not empty by any chance, skip quicker if it is.
549 549 vcs_instance = repo.scm_instance()
550 550 refs = vcs_instance.refs()
551 551 return refs
552 552 except Exception:
553 553 log.exception("Exception occurred while trying to get repo refs")
554 554 raise JSONRPCError(
555 555 'failed to get repo: `%s` references' % repo.repo_name
556 556 )
557 557
558 558
559 559 @jsonrpc_method()
560 560 def create_repo(
561 561 request, apiuser, repo_name, repo_type,
562 562 owner=Optional(OAttr('apiuser')),
563 563 description=Optional(''),
564 564 private=Optional(False),
565 565 clone_uri=Optional(None),
566 566 push_uri=Optional(None),
567 567 landing_rev=Optional('rev:tip'),
568 568 enable_statistics=Optional(False),
569 569 enable_locking=Optional(False),
570 570 enable_downloads=Optional(False),
571 571 copy_permissions=Optional(False)):
572 572 """
573 573 Creates a repository.
574 574
575 575 * If the repository name contains "/", repository will be created inside
576 576 a repository group or nested repository groups
577 577
578 578 For example "foo/bar/repo1" will create |repo| called "repo1" inside
579 579 group "foo/bar". You have to have permissions to access and write to
580 580 the last repository group ("bar" in this example)
581 581
582 582 This command can only be run using an |authtoken| with at least
583 583 permissions to create repositories, or write permissions to
584 584 parent repository groups.
585 585
586 586 :param apiuser: This is filled automatically from the |authtoken|.
587 587 :type apiuser: AuthUser
588 588 :param repo_name: Set the repository name.
589 589 :type repo_name: str
590 590 :param repo_type: Set the repository type; 'hg','git', or 'svn'.
591 591 :type repo_type: str
592 592 :param owner: user_id or username
593 593 :type owner: Optional(str)
594 594 :param description: Set the repository description.
595 595 :type description: Optional(str)
596 596 :param private: set repository as private
597 597 :type private: bool
598 598 :param clone_uri: set clone_uri
599 599 :type clone_uri: str
600 600 :param push_uri: set push_uri
601 601 :type push_uri: str
602 602 :param landing_rev: <rev_type>:<rev>
603 603 :type landing_rev: str
604 604 :param enable_locking:
605 605 :type enable_locking: bool
606 606 :param enable_downloads:
607 607 :type enable_downloads: bool
608 608 :param enable_statistics:
609 609 :type enable_statistics: bool
610 610 :param copy_permissions: Copy permission from group in which the
611 611 repository is being created.
612 612 :type copy_permissions: bool
613 613
614 614
615 615 Example output:
616 616
617 617 .. code-block:: bash
618 618
619 619 id : <id_given_in_input>
620 620 result: {
621 621 "msg": "Created new repository `<reponame>`",
622 622 "success": true,
623 623 "task": "<celery task id or None if done sync>"
624 624 }
625 625 error: null
626 626
627 627
628 628 Example error output:
629 629
630 630 .. code-block:: bash
631 631
632 632 id : <id_given_in_input>
633 633 result : null
634 634 error : {
635 635 'failed to create repository `<repo_name>`'
636 636 }
637 637
638 638 """
639 639
640 640 owner = validate_set_owner_permissions(apiuser, owner)
641 641
642 642 description = Optional.extract(description)
643 643 copy_permissions = Optional.extract(copy_permissions)
644 644 clone_uri = Optional.extract(clone_uri)
645 645 push_uri = Optional.extract(push_uri)
646 646 landing_commit_ref = Optional.extract(landing_rev)
647 647
648 648 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
649 649 if isinstance(private, Optional):
650 650 private = defs.get('repo_private') or Optional.extract(private)
651 651 if isinstance(repo_type, Optional):
652 652 repo_type = defs.get('repo_type')
653 653 if isinstance(enable_statistics, Optional):
654 654 enable_statistics = defs.get('repo_enable_statistics')
655 655 if isinstance(enable_locking, Optional):
656 656 enable_locking = defs.get('repo_enable_locking')
657 657 if isinstance(enable_downloads, Optional):
658 658 enable_downloads = defs.get('repo_enable_downloads')
659 659
660 660 schema = repo_schema.RepoSchema().bind(
661 661 repo_type_options=rhodecode.BACKENDS.keys(),
662 662 repo_type=repo_type,
663 663 # user caller
664 664 user=apiuser)
665 665
666 666 try:
667 667 schema_data = schema.deserialize(dict(
668 668 repo_name=repo_name,
669 669 repo_type=repo_type,
670 670 repo_owner=owner.username,
671 671 repo_description=description,
672 672 repo_landing_commit_ref=landing_commit_ref,
673 673 repo_clone_uri=clone_uri,
674 674 repo_push_uri=push_uri,
675 675 repo_private=private,
676 676 repo_copy_permissions=copy_permissions,
677 677 repo_enable_statistics=enable_statistics,
678 678 repo_enable_downloads=enable_downloads,
679 679 repo_enable_locking=enable_locking))
680 680 except validation_schema.Invalid as err:
681 681 raise JSONRPCValidationError(colander_exc=err)
682 682
683 683 try:
684 684 data = {
685 685 'owner': owner,
686 686 'repo_name': schema_data['repo_group']['repo_name_without_group'],
687 687 'repo_name_full': schema_data['repo_name'],
688 688 'repo_group': schema_data['repo_group']['repo_group_id'],
689 689 'repo_type': schema_data['repo_type'],
690 690 'repo_description': schema_data['repo_description'],
691 691 'repo_private': schema_data['repo_private'],
692 692 'clone_uri': schema_data['repo_clone_uri'],
693 693 'push_uri': schema_data['repo_push_uri'],
694 694 'repo_landing_rev': schema_data['repo_landing_commit_ref'],
695 695 'enable_statistics': schema_data['repo_enable_statistics'],
696 696 'enable_locking': schema_data['repo_enable_locking'],
697 697 'enable_downloads': schema_data['repo_enable_downloads'],
698 698 'repo_copy_permissions': schema_data['repo_copy_permissions'],
699 699 }
700 700
701 701 task = RepoModel().create(form_data=data, cur_user=owner.user_id)
702 702 task_id = get_task_id(task)
703 703 # no commit, it's done in RepoModel, or async via celery
704 704 return {
705 705 'msg': "Created new repository `%s`" % (schema_data['repo_name'],),
706 706 'success': True, # cannot return the repo data here since fork
707 707 # can be done async
708 708 'task': task_id
709 709 }
710 710 except Exception:
711 711 log.exception(
712 712 u"Exception while trying to create the repository %s",
713 713 schema_data['repo_name'])
714 714 raise JSONRPCError(
715 715 'failed to create repository `%s`' % (schema_data['repo_name'],))
716 716
717 717
718 718 @jsonrpc_method()
719 719 def add_field_to_repo(request, apiuser, repoid, key, label=Optional(''),
720 720 description=Optional('')):
721 721 """
722 722 Adds an extra field to a repository.
723 723
724 724 This command can only be run using an |authtoken| with at least
725 725 write permissions to the |repo|.
726 726
727 727 :param apiuser: This is filled automatically from the |authtoken|.
728 728 :type apiuser: AuthUser
729 729 :param repoid: Set the repository name or repository id.
730 730 :type repoid: str or int
731 731 :param key: Create a unique field key for this repository.
732 732 :type key: str
733 733 :param label:
734 734 :type label: Optional(str)
735 735 :param description:
736 736 :type description: Optional(str)
737 737 """
738 738 repo = get_repo_or_error(repoid)
739 739 if not has_superadmin_permission(apiuser):
740 740 _perms = ('repository.admin',)
741 741 validate_repo_permissions(apiuser, repoid, repo, _perms)
742 742
743 743 label = Optional.extract(label) or key
744 744 description = Optional.extract(description)
745 745
746 746 field = RepositoryField.get_by_key_name(key, repo)
747 747 if field:
748 748 raise JSONRPCError('Field with key '
749 749 '`%s` exists for repo `%s`' % (key, repoid))
750 750
751 751 try:
752 752 RepoModel().add_repo_field(repo, key, field_label=label,
753 753 field_desc=description)
754 754 Session().commit()
755 755 return {
756 756 'msg': "Added new repository field `%s`" % (key,),
757 757 'success': True,
758 758 }
759 759 except Exception:
760 760 log.exception("Exception occurred while trying to add field to repo")
761 761 raise JSONRPCError(
762 762 'failed to create new field for repository `%s`' % (repoid,))
763 763
764 764
765 765 @jsonrpc_method()
766 766 def remove_field_from_repo(request, apiuser, repoid, key):
767 767 """
768 768 Removes an extra field from a repository.
769 769
770 770 This command can only be run using an |authtoken| with at least
771 771 write permissions to the |repo|.
772 772
773 773 :param apiuser: This is filled automatically from the |authtoken|.
774 774 :type apiuser: AuthUser
775 775 :param repoid: Set the repository name or repository ID.
776 776 :type repoid: str or int
777 777 :param key: Set the unique field key for this repository.
778 778 :type key: str
779 779 """
780 780
781 781 repo = get_repo_or_error(repoid)
782 782 if not has_superadmin_permission(apiuser):
783 783 _perms = ('repository.admin',)
784 784 validate_repo_permissions(apiuser, repoid, repo, _perms)
785 785
786 786 field = RepositoryField.get_by_key_name(key, repo)
787 787 if not field:
788 788 raise JSONRPCError('Field with key `%s` does not '
789 789 'exists for repo `%s`' % (key, repoid))
790 790
791 791 try:
792 792 RepoModel().delete_repo_field(repo, field_key=key)
793 793 Session().commit()
794 794 return {
795 795 'msg': "Deleted repository field `%s`" % (key,),
796 796 'success': True,
797 797 }
798 798 except Exception:
799 799 log.exception(
800 800 "Exception occurred while trying to delete field from repo")
801 801 raise JSONRPCError(
802 802 'failed to delete field for repository `%s`' % (repoid,))
803 803
804 804
805 805 @jsonrpc_method()
806 806 def update_repo(
807 807 request, apiuser, repoid, repo_name=Optional(None),
808 808 owner=Optional(OAttr('apiuser')), description=Optional(''),
809 809 private=Optional(False),
810 810 clone_uri=Optional(None), push_uri=Optional(None),
811 811 landing_rev=Optional('rev:tip'), fork_of=Optional(None),
812 812 enable_statistics=Optional(False),
813 813 enable_locking=Optional(False),
814 814 enable_downloads=Optional(False), fields=Optional('')):
815 815 """
816 816 Updates a repository with the given information.
817 817
818 818 This command can only be run using an |authtoken| with at least
819 819 admin permissions to the |repo|.
820 820
821 821 * If the repository name contains "/", repository will be updated
822 822 accordingly with a repository group or nested repository groups
823 823
824 824 For example repoid=repo-test name="foo/bar/repo-test" will update |repo|
825 825 called "repo-test" and place it inside group "foo/bar".
826 826 You have to have permissions to access and write to the last repository
827 827 group ("bar" in this example)
828 828
829 829 :param apiuser: This is filled automatically from the |authtoken|.
830 830 :type apiuser: AuthUser
831 831 :param repoid: repository name or repository ID.
832 832 :type repoid: str or int
833 833 :param repo_name: Update the |repo| name, including the
834 834 repository group it's in.
835 835 :type repo_name: str
836 836 :param owner: Set the |repo| owner.
837 837 :type owner: str
838 838 :param fork_of: Set the |repo| as fork of another |repo|.
839 839 :type fork_of: str
840 840 :param description: Update the |repo| description.
841 841 :type description: str
842 842 :param private: Set the |repo| as private. (True | False)
843 843 :type private: bool
844 844 :param clone_uri: Update the |repo| clone URI.
845 845 :type clone_uri: str
846 846 :param landing_rev: Set the |repo| landing revision. Default is ``rev:tip``.
847 847 :type landing_rev: str
848 848 :param enable_statistics: Enable statistics on the |repo|, (True | False).
849 849 :type enable_statistics: bool
850 850 :param enable_locking: Enable |repo| locking.
851 851 :type enable_locking: bool
852 852 :param enable_downloads: Enable downloads from the |repo|, (True | False).
853 853 :type enable_downloads: bool
854 854 :param fields: Add extra fields to the |repo|. Use the following
855 855 example format: ``field_key=field_val,field_key2=fieldval2``.
856 856 Escape ', ' with \,
857 857 :type fields: str
858 858 """
859 859
860 860 repo = get_repo_or_error(repoid)
861 861
862 862 include_secrets = False
863 863 if not has_superadmin_permission(apiuser):
864 864 validate_repo_permissions(apiuser, repoid, repo, ('repository.admin',))
865 865 else:
866 866 include_secrets = True
867 867
868 868 updates = dict(
869 869 repo_name=repo_name
870 870 if not isinstance(repo_name, Optional) else repo.repo_name,
871 871
872 872 fork_id=fork_of
873 873 if not isinstance(fork_of, Optional) else repo.fork.repo_name if repo.fork else None,
874 874
875 875 user=owner
876 876 if not isinstance(owner, Optional) else repo.user.username,
877 877
878 878 repo_description=description
879 879 if not isinstance(description, Optional) else repo.description,
880 880
881 881 repo_private=private
882 882 if not isinstance(private, Optional) else repo.private,
883 883
884 884 clone_uri=clone_uri
885 885 if not isinstance(clone_uri, Optional) else repo.clone_uri,
886 886
887 887 push_uri=push_uri
888 888 if not isinstance(push_uri, Optional) else repo.push_uri,
889 889
890 890 repo_landing_rev=landing_rev
891 891 if not isinstance(landing_rev, Optional) else repo._landing_revision,
892 892
893 893 repo_enable_statistics=enable_statistics
894 894 if not isinstance(enable_statistics, Optional) else repo.enable_statistics,
895 895
896 896 repo_enable_locking=enable_locking
897 897 if not isinstance(enable_locking, Optional) else repo.enable_locking,
898 898
899 899 repo_enable_downloads=enable_downloads
900 900 if not isinstance(enable_downloads, Optional) else repo.enable_downloads)
901 901
902 902 ref_choices, _labels = ScmModel().get_repo_landing_revs(
903 903 request.translate, repo=repo)
904 904
905 905 old_values = repo.get_api_data()
906 906 repo_type = repo.repo_type
907 907 schema = repo_schema.RepoSchema().bind(
908 908 repo_type_options=rhodecode.BACKENDS.keys(),
909 909 repo_ref_options=ref_choices,
910 910 repo_type=repo_type,
911 911 # user caller
912 912 user=apiuser,
913 913 old_values=old_values)
914 914 try:
915 915 schema_data = schema.deserialize(dict(
916 916 # we save old value, users cannot change type
917 917 repo_type=repo_type,
918 918
919 919 repo_name=updates['repo_name'],
920 920 repo_owner=updates['user'],
921 921 repo_description=updates['repo_description'],
922 922 repo_clone_uri=updates['clone_uri'],
923 923 repo_push_uri=updates['push_uri'],
924 924 repo_fork_of=updates['fork_id'],
925 925 repo_private=updates['repo_private'],
926 926 repo_landing_commit_ref=updates['repo_landing_rev'],
927 927 repo_enable_statistics=updates['repo_enable_statistics'],
928 928 repo_enable_downloads=updates['repo_enable_downloads'],
929 929 repo_enable_locking=updates['repo_enable_locking']))
930 930 except validation_schema.Invalid as err:
931 931 raise JSONRPCValidationError(colander_exc=err)
932 932
933 933 # save validated data back into the updates dict
934 934 validated_updates = dict(
935 935 repo_name=schema_data['repo_group']['repo_name_without_group'],
936 936 repo_group=schema_data['repo_group']['repo_group_id'],
937 937
938 938 user=schema_data['repo_owner'],
939 939 repo_description=schema_data['repo_description'],
940 940 repo_private=schema_data['repo_private'],
941 941 clone_uri=schema_data['repo_clone_uri'],
942 942 push_uri=schema_data['repo_push_uri'],
943 943 repo_landing_rev=schema_data['repo_landing_commit_ref'],
944 944 repo_enable_statistics=schema_data['repo_enable_statistics'],
945 945 repo_enable_locking=schema_data['repo_enable_locking'],
946 946 repo_enable_downloads=schema_data['repo_enable_downloads'],
947 947 )
948 948
949 949 if schema_data['repo_fork_of']:
950 950 fork_repo = get_repo_or_error(schema_data['repo_fork_of'])
951 951 validated_updates['fork_id'] = fork_repo.repo_id
952 952
953 953 # extra fields
954 954 fields = parse_args(Optional.extract(fields), key_prefix='ex_')
955 955 if fields:
956 956 validated_updates.update(fields)
957 957
958 958 try:
959 959 RepoModel().update(repo, **validated_updates)
960 960 audit_logger.store_api(
961 961 'repo.edit', action_data={'old_data': old_values},
962 962 user=apiuser, repo=repo)
963 963 Session().commit()
964 964 return {
965 965 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
966 966 'repository': repo.get_api_data(include_secrets=include_secrets)
967 967 }
968 968 except Exception:
969 969 log.exception(
970 970 u"Exception while trying to update the repository %s",
971 971 repoid)
972 972 raise JSONRPCError('failed to update repo `%s`' % repoid)
973 973
974 974
975 975 @jsonrpc_method()
976 976 def fork_repo(request, apiuser, repoid, fork_name,
977 977 owner=Optional(OAttr('apiuser')),
978 978 description=Optional(''),
979 979 private=Optional(False),
980 980 clone_uri=Optional(None),
981 981 landing_rev=Optional('rev:tip'),
982 982 copy_permissions=Optional(False)):
983 983 """
984 984 Creates a fork of the specified |repo|.
985 985
986 986 * If the fork_name contains "/", fork will be created inside
987 987 a repository group or nested repository groups
988 988
989 989 For example "foo/bar/fork-repo" will create fork called "fork-repo"
990 990 inside group "foo/bar". You have to have permissions to access and
991 991 write to the last repository group ("bar" in this example)
992 992
993 993 This command can only be run using an |authtoken| with minimum
994 994 read permissions of the forked repo, create fork permissions for an user.
995 995
996 996 :param apiuser: This is filled automatically from the |authtoken|.
997 997 :type apiuser: AuthUser
998 998 :param repoid: Set repository name or repository ID.
999 999 :type repoid: str or int
1000 1000 :param fork_name: Set the fork name, including it's repository group membership.
1001 1001 :type fork_name: str
1002 1002 :param owner: Set the fork owner.
1003 1003 :type owner: str
1004 1004 :param description: Set the fork description.
1005 1005 :type description: str
1006 1006 :param copy_permissions: Copy permissions from parent |repo|. The
1007 1007 default is False.
1008 1008 :type copy_permissions: bool
1009 1009 :param private: Make the fork private. The default is False.
1010 1010 :type private: bool
1011 1011 :param landing_rev: Set the landing revision. The default is tip.
1012 1012
1013 1013 Example output:
1014 1014
1015 1015 .. code-block:: bash
1016 1016
1017 1017 id : <id_for_response>
1018 1018 api_key : "<api_key>"
1019 1019 args: {
1020 1020 "repoid" : "<reponame or repo_id>",
1021 1021 "fork_name": "<forkname>",
1022 1022 "owner": "<username or user_id = Optional(=apiuser)>",
1023 1023 "description": "<description>",
1024 1024 "copy_permissions": "<bool>",
1025 1025 "private": "<bool>",
1026 1026 "landing_rev": "<landing_rev>"
1027 1027 }
1028 1028
1029 1029 Example error output:
1030 1030
1031 1031 .. code-block:: bash
1032 1032
1033 1033 id : <id_given_in_input>
1034 1034 result: {
1035 1035 "msg": "Created fork of `<reponame>` as `<forkname>`",
1036 1036 "success": true,
1037 1037 "task": "<celery task id or None if done sync>"
1038 1038 }
1039 1039 error: null
1040 1040
1041 1041 """
1042 1042
1043 1043 repo = get_repo_or_error(repoid)
1044 1044 repo_name = repo.repo_name
1045 1045
1046 1046 if not has_superadmin_permission(apiuser):
1047 1047 # check if we have at least read permission for
1048 1048 # this repo that we fork !
1049 1049 _perms = (
1050 1050 'repository.admin', 'repository.write', 'repository.read')
1051 1051 validate_repo_permissions(apiuser, repoid, repo, _perms)
1052 1052
1053 1053 # check if the regular user has at least fork permissions as well
1054 1054 if not HasPermissionAnyApi('hg.fork.repository')(user=apiuser):
1055 1055 raise JSONRPCForbidden()
1056 1056
1057 1057 # check if user can set owner parameter
1058 1058 owner = validate_set_owner_permissions(apiuser, owner)
1059 1059
1060 1060 description = Optional.extract(description)
1061 1061 copy_permissions = Optional.extract(copy_permissions)
1062 1062 clone_uri = Optional.extract(clone_uri)
1063 1063 landing_commit_ref = Optional.extract(landing_rev)
1064 1064 private = Optional.extract(private)
1065 1065
1066 1066 schema = repo_schema.RepoSchema().bind(
1067 1067 repo_type_options=rhodecode.BACKENDS.keys(),
1068 1068 repo_type=repo.repo_type,
1069 1069 # user caller
1070 1070 user=apiuser)
1071 1071
1072 1072 try:
1073 1073 schema_data = schema.deserialize(dict(
1074 1074 repo_name=fork_name,
1075 1075 repo_type=repo.repo_type,
1076 1076 repo_owner=owner.username,
1077 1077 repo_description=description,
1078 1078 repo_landing_commit_ref=landing_commit_ref,
1079 1079 repo_clone_uri=clone_uri,
1080 1080 repo_private=private,
1081 1081 repo_copy_permissions=copy_permissions))
1082 1082 except validation_schema.Invalid as err:
1083 1083 raise JSONRPCValidationError(colander_exc=err)
1084 1084
1085 1085 try:
1086 1086 data = {
1087 1087 'fork_parent_id': repo.repo_id,
1088 1088
1089 1089 'repo_name': schema_data['repo_group']['repo_name_without_group'],
1090 1090 'repo_name_full': schema_data['repo_name'],
1091 1091 'repo_group': schema_data['repo_group']['repo_group_id'],
1092 1092 'repo_type': schema_data['repo_type'],
1093 1093 'description': schema_data['repo_description'],
1094 1094 'private': schema_data['repo_private'],
1095 1095 'copy_permissions': schema_data['repo_copy_permissions'],
1096 1096 'landing_rev': schema_data['repo_landing_commit_ref'],
1097 1097 }
1098 1098
1099 1099 task = RepoModel().create_fork(data, cur_user=owner.user_id)
1100 1100 # no commit, it's done in RepoModel, or async via celery
1101 1101 task_id = get_task_id(task)
1102 1102
1103 1103 return {
1104 1104 'msg': 'Created fork of `%s` as `%s`' % (
1105 1105 repo.repo_name, schema_data['repo_name']),
1106 1106 'success': True, # cannot return the repo data here since fork
1107 1107 # can be done async
1108 1108 'task': task_id
1109 1109 }
1110 1110 except Exception:
1111 1111 log.exception(
1112 1112 u"Exception while trying to create fork %s",
1113 1113 schema_data['repo_name'])
1114 1114 raise JSONRPCError(
1115 1115 'failed to fork repository `%s` as `%s`' % (
1116 1116 repo_name, schema_data['repo_name']))
1117 1117
1118 1118
1119 1119 @jsonrpc_method()
1120 1120 def delete_repo(request, apiuser, repoid, forks=Optional('')):
1121 1121 """
1122 1122 Deletes a repository.
1123 1123
1124 1124 * When the `forks` parameter is set it's possible to detach or delete
1125 1125 forks of deleted repository.
1126 1126
1127 1127 This command can only be run using an |authtoken| with admin
1128 1128 permissions on the |repo|.
1129 1129
1130 1130 :param apiuser: This is filled automatically from the |authtoken|.
1131 1131 :type apiuser: AuthUser
1132 1132 :param repoid: Set the repository name or repository ID.
1133 1133 :type repoid: str or int
1134 1134 :param forks: Set to `detach` or `delete` forks from the |repo|.
1135 1135 :type forks: Optional(str)
1136 1136
1137 1137 Example error output:
1138 1138
1139 1139 .. code-block:: bash
1140 1140
1141 1141 id : <id_given_in_input>
1142 1142 result: {
1143 1143 "msg": "Deleted repository `<reponame>`",
1144 1144 "success": true
1145 1145 }
1146 1146 error: null
1147 1147 """
1148 1148
1149 1149 repo = get_repo_or_error(repoid)
1150 1150 repo_name = repo.repo_name
1151 1151 if not has_superadmin_permission(apiuser):
1152 1152 _perms = ('repository.admin',)
1153 1153 validate_repo_permissions(apiuser, repoid, repo, _perms)
1154 1154
1155 1155 try:
1156 1156 handle_forks = Optional.extract(forks)
1157 1157 _forks_msg = ''
1158 1158 _forks = [f for f in repo.forks]
1159 1159 if handle_forks == 'detach':
1160 1160 _forks_msg = ' ' + 'Detached %s forks' % len(_forks)
1161 1161 elif handle_forks == 'delete':
1162 1162 _forks_msg = ' ' + 'Deleted %s forks' % len(_forks)
1163 1163 elif _forks:
1164 1164 raise JSONRPCError(
1165 1165 'Cannot delete `%s` it still contains attached forks' %
1166 1166 (repo.repo_name,)
1167 1167 )
1168 1168 old_data = repo.get_api_data()
1169 1169 RepoModel().delete(repo, forks=forks)
1170 1170
1171 1171 repo = audit_logger.RepoWrap(repo_id=None,
1172 1172 repo_name=repo.repo_name)
1173 1173
1174 1174 audit_logger.store_api(
1175 1175 'repo.delete', action_data={'old_data': old_data},
1176 1176 user=apiuser, repo=repo)
1177 1177
1178 1178 ScmModel().mark_for_invalidation(repo_name, delete=True)
1179 1179 Session().commit()
1180 1180 return {
1181 1181 'msg': 'Deleted repository `%s`%s' % (repo_name, _forks_msg),
1182 1182 'success': True
1183 1183 }
1184 1184 except Exception:
1185 1185 log.exception("Exception occurred while trying to delete repo")
1186 1186 raise JSONRPCError(
1187 1187 'failed to delete repository `%s`' % (repo_name,)
1188 1188 )
1189 1189
1190 1190
1191 1191 #TODO: marcink, change name ?
1192 1192 @jsonrpc_method()
1193 1193 def invalidate_cache(request, apiuser, repoid, delete_keys=Optional(False)):
1194 1194 """
1195 1195 Invalidates the cache for the specified repository.
1196 1196
1197 1197 This command can only be run using an |authtoken| with admin rights to
1198 1198 the specified repository.
1199 1199
1200 1200 This command takes the following options:
1201 1201
1202 1202 :param apiuser: This is filled automatically from |authtoken|.
1203 1203 :type apiuser: AuthUser
1204 1204 :param repoid: Sets the repository name or repository ID.
1205 1205 :type repoid: str or int
1206 1206 :param delete_keys: This deletes the invalidated keys instead of
1207 1207 just flagging them.
1208 1208 :type delete_keys: Optional(``True`` | ``False``)
1209 1209
1210 1210 Example output:
1211 1211
1212 1212 .. code-block:: bash
1213 1213
1214 1214 id : <id_given_in_input>
1215 1215 result : {
1216 1216 'msg': Cache for repository `<repository name>` was invalidated,
1217 1217 'repository': <repository name>
1218 1218 }
1219 1219 error : null
1220 1220
1221 1221 Example error output:
1222 1222
1223 1223 .. code-block:: bash
1224 1224
1225 1225 id : <id_given_in_input>
1226 1226 result : null
1227 1227 error : {
1228 1228 'Error occurred during cache invalidation action'
1229 1229 }
1230 1230
1231 1231 """
1232 1232
1233 1233 repo = get_repo_or_error(repoid)
1234 1234 if not has_superadmin_permission(apiuser):
1235 1235 _perms = ('repository.admin', 'repository.write',)
1236 1236 validate_repo_permissions(apiuser, repoid, repo, _perms)
1237 1237
1238 1238 delete = Optional.extract(delete_keys)
1239 1239 try:
1240 1240 ScmModel().mark_for_invalidation(repo.repo_name, delete=delete)
1241 1241 return {
1242 1242 'msg': 'Cache for repository `%s` was invalidated' % (repoid,),
1243 1243 'repository': repo.repo_name
1244 1244 }
1245 1245 except Exception:
1246 1246 log.exception(
1247 1247 "Exception occurred while trying to invalidate repo cache")
1248 1248 raise JSONRPCError(
1249 1249 'Error occurred during cache invalidation action'
1250 1250 )
1251 1251
1252 1252
1253 1253 #TODO: marcink, change name ?
1254 1254 @jsonrpc_method()
1255 1255 def lock(request, apiuser, repoid, locked=Optional(None),
1256 1256 userid=Optional(OAttr('apiuser'))):
1257 1257 """
1258 1258 Sets the lock state of the specified |repo| by the given user.
1259 1259 From more information, see :ref:`repo-locking`.
1260 1260
1261 1261 * If the ``userid`` option is not set, the repository is locked to the
1262 1262 user who called the method.
1263 1263 * If the ``locked`` parameter is not set, the current lock state of the
1264 1264 repository is displayed.
1265 1265
1266 1266 This command can only be run using an |authtoken| with admin rights to
1267 1267 the specified repository.
1268 1268
1269 1269 This command takes the following options:
1270 1270
1271 1271 :param apiuser: This is filled automatically from the |authtoken|.
1272 1272 :type apiuser: AuthUser
1273 1273 :param repoid: Sets the repository name or repository ID.
1274 1274 :type repoid: str or int
1275 1275 :param locked: Sets the lock state.
1276 1276 :type locked: Optional(``True`` | ``False``)
1277 1277 :param userid: Set the repository lock to this user.
1278 1278 :type userid: Optional(str or int)
1279 1279
1280 1280 Example error output:
1281 1281
1282 1282 .. code-block:: bash
1283 1283
1284 1284 id : <id_given_in_input>
1285 1285 result : {
1286 1286 'repo': '<reponame>',
1287 1287 'locked': <bool: lock state>,
1288 1288 'locked_since': <int: lock timestamp>,
1289 1289 'locked_by': <username of person who made the lock>,
1290 1290 'lock_reason': <str: reason for locking>,
1291 1291 'lock_state_changed': <bool: True if lock state has been changed in this request>,
1292 1292 'msg': 'Repo `<reponame>` locked by `<username>` on <timestamp>.'
1293 1293 or
1294 1294 'msg': 'Repo `<repository name>` not locked.'
1295 1295 or
1296 1296 'msg': 'User `<user name>` set lock state for repo `<repository name>` to `<new lock state>`'
1297 1297 }
1298 1298 error : null
1299 1299
1300 1300 Example error output:
1301 1301
1302 1302 .. code-block:: bash
1303 1303
1304 1304 id : <id_given_in_input>
1305 1305 result : null
1306 1306 error : {
1307 1307 'Error occurred locking repository `<reponame>`'
1308 1308 }
1309 1309 """
1310 1310
1311 1311 repo = get_repo_or_error(repoid)
1312 1312 if not has_superadmin_permission(apiuser):
1313 1313 # check if we have at least write permission for this repo !
1314 1314 _perms = ('repository.admin', 'repository.write',)
1315 1315 validate_repo_permissions(apiuser, repoid, repo, _perms)
1316 1316
1317 1317 # make sure normal user does not pass someone else userid,
1318 1318 # he is not allowed to do that
1319 1319 if not isinstance(userid, Optional) and userid != apiuser.user_id:
1320 1320 raise JSONRPCError('userid is not the same as your user')
1321 1321
1322 1322 if isinstance(userid, Optional):
1323 1323 userid = apiuser.user_id
1324 1324
1325 1325 user = get_user_or_error(userid)
1326 1326
1327 1327 if isinstance(locked, Optional):
1328 1328 lockobj = repo.locked
1329 1329
1330 1330 if lockobj[0] is None:
1331 1331 _d = {
1332 1332 'repo': repo.repo_name,
1333 1333 'locked': False,
1334 1334 'locked_since': None,
1335 1335 'locked_by': None,
1336 1336 'lock_reason': None,
1337 1337 'lock_state_changed': False,
1338 1338 'msg': 'Repo `%s` not locked.' % repo.repo_name
1339 1339 }
1340 1340 return _d
1341 1341 else:
1342 1342 _user_id, _time, _reason = lockobj
1343 1343 lock_user = get_user_or_error(userid)
1344 1344 _d = {
1345 1345 'repo': repo.repo_name,
1346 1346 'locked': True,
1347 1347 'locked_since': _time,
1348 1348 'locked_by': lock_user.username,
1349 1349 'lock_reason': _reason,
1350 1350 'lock_state_changed': False,
1351 1351 'msg': ('Repo `%s` locked by `%s` on `%s`.'
1352 1352 % (repo.repo_name, lock_user.username,
1353 1353 json.dumps(time_to_datetime(_time))))
1354 1354 }
1355 1355 return _d
1356 1356
1357 1357 # force locked state through a flag
1358 1358 else:
1359 1359 locked = str2bool(locked)
1360 1360 lock_reason = Repository.LOCK_API
1361 1361 try:
1362 1362 if locked:
1363 1363 lock_time = time.time()
1364 1364 Repository.lock(repo, user.user_id, lock_time, lock_reason)
1365 1365 else:
1366 1366 lock_time = None
1367 1367 Repository.unlock(repo)
1368 1368 _d = {
1369 1369 'repo': repo.repo_name,
1370 1370 'locked': locked,
1371 1371 'locked_since': lock_time,
1372 1372 'locked_by': user.username,
1373 1373 'lock_reason': lock_reason,
1374 1374 'lock_state_changed': True,
1375 1375 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
1376 1376 % (user.username, repo.repo_name, locked))
1377 1377 }
1378 1378 return _d
1379 1379 except Exception:
1380 1380 log.exception(
1381 1381 "Exception occurred while trying to lock repository")
1382 1382 raise JSONRPCError(
1383 1383 'Error occurred locking repository `%s`' % repo.repo_name
1384 1384 )
1385 1385
1386 1386
1387 1387 @jsonrpc_method()
1388 1388 def comment_commit(
1389 1389 request, apiuser, repoid, commit_id, message, status=Optional(None),
1390 1390 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
1391 1391 resolves_comment_id=Optional(None),
1392 1392 userid=Optional(OAttr('apiuser'))):
1393 1393 """
1394 1394 Set a commit comment, and optionally change the status of the commit.
1395 1395
1396 1396 :param apiuser: This is filled automatically from the |authtoken|.
1397 1397 :type apiuser: AuthUser
1398 1398 :param repoid: Set the repository name or repository ID.
1399 1399 :type repoid: str or int
1400 1400 :param commit_id: Specify the commit_id for which to set a comment.
1401 1401 :type commit_id: str
1402 1402 :param message: The comment text.
1403 1403 :type message: str
1404 1404 :param status: (**Optional**) status of commit, one of: 'not_reviewed',
1405 1405 'approved', 'rejected', 'under_review'
1406 1406 :type status: str
1407 1407 :param comment_type: Comment type, one of: 'note', 'todo'
1408 1408 :type comment_type: Optional(str), default: 'note'
1409 1409 :param userid: Set the user name of the comment creator.
1410 1410 :type userid: Optional(str or int)
1411 1411
1412 1412 Example error output:
1413 1413
1414 1414 .. code-block:: bash
1415 1415
1416 1416 {
1417 1417 "id" : <id_given_in_input>,
1418 1418 "result" : {
1419 1419 "msg": "Commented on commit `<commit_id>` for repository `<repoid>`",
1420 1420 "status_change": null or <status>,
1421 1421 "success": true
1422 1422 },
1423 1423 "error" : null
1424 1424 }
1425 1425
1426 1426 """
1427 1427 repo = get_repo_or_error(repoid)
1428 1428 if not has_superadmin_permission(apiuser):
1429 1429 _perms = ('repository.read', 'repository.write', 'repository.admin')
1430 1430 validate_repo_permissions(apiuser, repoid, repo, _perms)
1431 1431
1432 1432 try:
1433 1433 commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id
1434 1434 except Exception as e:
1435 1435 log.exception('Failed to fetch commit')
1436 1436 raise JSONRPCError(safe_str(e))
1437 1437
1438 1438 if isinstance(userid, Optional):
1439 1439 userid = apiuser.user_id
1440 1440
1441 1441 user = get_user_or_error(userid)
1442 1442 status = Optional.extract(status)
1443 1443 comment_type = Optional.extract(comment_type)
1444 1444 resolves_comment_id = Optional.extract(resolves_comment_id)
1445 1445
1446 1446 allowed_statuses = [x[0] for x in ChangesetStatus.STATUSES]
1447 1447 if status and status not in allowed_statuses:
1448 1448 raise JSONRPCError('Bad status, must be on '
1449 1449 'of %s got %s' % (allowed_statuses, status,))
1450 1450
1451 1451 if resolves_comment_id:
1452 1452 comment = ChangesetComment.get(resolves_comment_id)
1453 1453 if not comment:
1454 1454 raise JSONRPCError(
1455 1455 'Invalid resolves_comment_id `%s` for this commit.'
1456 1456 % resolves_comment_id)
1457 1457 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
1458 1458 raise JSONRPCError(
1459 1459 'Comment `%s` is wrong type for setting status to resolved.'
1460 1460 % resolves_comment_id)
1461 1461
1462 1462 try:
1463 1463 rc_config = SettingsModel().get_all_settings()
1464 1464 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
1465 1465 status_change_label = ChangesetStatus.get_status_lbl(status)
1466 1466 comment = CommentsModel().create(
1467 1467 message, repo, user, commit_id=commit_id,
1468 1468 status_change=status_change_label,
1469 1469 status_change_type=status,
1470 1470 renderer=renderer,
1471 1471 comment_type=comment_type,
1472 1472 resolves_comment_id=resolves_comment_id,
1473 1473 auth_user=apiuser
1474 1474 )
1475 1475 if status:
1476 1476 # also do a status change
1477 1477 try:
1478 1478 ChangesetStatusModel().set_status(
1479 1479 repo, status, user, comment, revision=commit_id,
1480 1480 dont_allow_on_closed_pull_request=True
1481 1481 )
1482 1482 except StatusChangeOnClosedPullRequestError:
1483 1483 log.exception(
1484 1484 "Exception occurred while trying to change repo commit status")
1485 1485 msg = ('Changing status on a changeset associated with '
1486 1486 'a closed pull request is not allowed')
1487 1487 raise JSONRPCError(msg)
1488 1488
1489 1489 Session().commit()
1490 1490 return {
1491 1491 'msg': (
1492 1492 'Commented on commit `%s` for repository `%s`' % (
1493 1493 comment.revision, repo.repo_name)),
1494 1494 'status_change': status,
1495 1495 'success': True,
1496 1496 }
1497 1497 except JSONRPCError:
1498 1498 # catch any inside errors, and re-raise them to prevent from
1499 1499 # below global catch to silence them
1500 1500 raise
1501 1501 except Exception:
1502 1502 log.exception("Exception occurred while trying to comment on commit")
1503 1503 raise JSONRPCError(
1504 1504 'failed to set comment on repository `%s`' % (repo.repo_name,)
1505 1505 )
1506 1506
1507 1507
1508 1508 @jsonrpc_method()
1509 1509 def grant_user_permission(request, apiuser, repoid, userid, perm):
1510 1510 """
1511 1511 Grant permissions for the specified user on the given repository,
1512 1512 or update existing permissions if found.
1513 1513
1514 1514 This command can only be run using an |authtoken| with admin
1515 1515 permissions on the |repo|.
1516 1516
1517 1517 :param apiuser: This is filled automatically from the |authtoken|.
1518 1518 :type apiuser: AuthUser
1519 1519 :param repoid: Set the repository name or repository ID.
1520 1520 :type repoid: str or int
1521 1521 :param userid: Set the user name.
1522 1522 :type userid: str
1523 1523 :param perm: Set the user permissions, using the following format
1524 1524 ``(repository.(none|read|write|admin))``
1525 1525 :type perm: str
1526 1526
1527 1527 Example output:
1528 1528
1529 1529 .. code-block:: bash
1530 1530
1531 1531 id : <id_given_in_input>
1532 1532 result: {
1533 1533 "msg" : "Granted perm: `<perm>` for user: `<username>` in repo: `<reponame>`",
1534 1534 "success": true
1535 1535 }
1536 1536 error: null
1537 1537 """
1538 1538
1539 1539 repo = get_repo_or_error(repoid)
1540 1540 user = get_user_or_error(userid)
1541 1541 perm = get_perm_or_error(perm)
1542 1542 if not has_superadmin_permission(apiuser):
1543 1543 _perms = ('repository.admin',)
1544 1544 validate_repo_permissions(apiuser, repoid, repo, _perms)
1545 1545
1546 1546 perm_additions = [[user.user_id, perm.permission_name, "user"]]
1547 1547 try:
1548 1548 changes = RepoModel().update_permissions(
1549 1549 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1550 1550
1551 1551 action_data = {
1552 1552 'added': changes['added'],
1553 1553 'updated': changes['updated'],
1554 1554 'deleted': changes['deleted'],
1555 1555 }
1556 1556 audit_logger.store_api(
1557 1557 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1558 1558
1559 1559 Session().commit()
1560 1560 return {
1561 1561 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
1562 1562 perm.permission_name, user.username, repo.repo_name
1563 1563 ),
1564 1564 'success': True
1565 1565 }
1566 1566 except Exception:
1567 1567 log.exception("Exception occurred while trying edit permissions for repo")
1568 1568 raise JSONRPCError(
1569 1569 'failed to edit permission for user: `%s` in repo: `%s`' % (
1570 1570 userid, repoid
1571 1571 )
1572 1572 )
1573 1573
1574 1574
1575 1575 @jsonrpc_method()
1576 1576 def revoke_user_permission(request, apiuser, repoid, userid):
1577 1577 """
1578 1578 Revoke permission for a user on the specified repository.
1579 1579
1580 1580 This command can only be run using an |authtoken| with admin
1581 1581 permissions on the |repo|.
1582 1582
1583 1583 :param apiuser: This is filled automatically from the |authtoken|.
1584 1584 :type apiuser: AuthUser
1585 1585 :param repoid: Set the repository name or repository ID.
1586 1586 :type repoid: str or int
1587 1587 :param userid: Set the user name of revoked user.
1588 1588 :type userid: str or int
1589 1589
1590 1590 Example error output:
1591 1591
1592 1592 .. code-block:: bash
1593 1593
1594 1594 id : <id_given_in_input>
1595 1595 result: {
1596 1596 "msg" : "Revoked perm for user: `<username>` in repo: `<reponame>`",
1597 1597 "success": true
1598 1598 }
1599 1599 error: null
1600 1600 """
1601 1601
1602 1602 repo = get_repo_or_error(repoid)
1603 1603 user = get_user_or_error(userid)
1604 1604 if not has_superadmin_permission(apiuser):
1605 1605 _perms = ('repository.admin',)
1606 1606 validate_repo_permissions(apiuser, repoid, repo, _perms)
1607 1607
1608 1608 perm_deletions = [[user.user_id, None, "user"]]
1609 1609 try:
1610 1610 changes = RepoModel().update_permissions(
1611 1611 repo=repo, perm_deletions=perm_deletions, cur_user=user)
1612 1612
1613 1613 action_data = {
1614 1614 'added': changes['added'],
1615 1615 'updated': changes['updated'],
1616 1616 'deleted': changes['deleted'],
1617 1617 }
1618 1618 audit_logger.store_api(
1619 1619 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1620 1620
1621 1621 Session().commit()
1622 1622 return {
1623 1623 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
1624 1624 user.username, repo.repo_name
1625 1625 ),
1626 1626 'success': True
1627 1627 }
1628 1628 except Exception:
1629 1629 log.exception("Exception occurred while trying revoke permissions to repo")
1630 1630 raise JSONRPCError(
1631 1631 'failed to edit permission for user: `%s` in repo: `%s`' % (
1632 1632 userid, repoid
1633 1633 )
1634 1634 )
1635 1635
1636 1636
1637 1637 @jsonrpc_method()
1638 1638 def grant_user_group_permission(request, apiuser, repoid, usergroupid, perm):
1639 1639 """
1640 1640 Grant permission for a user group on the specified repository,
1641 1641 or update existing permissions.
1642 1642
1643 1643 This command can only be run using an |authtoken| with admin
1644 1644 permissions on the |repo|.
1645 1645
1646 1646 :param apiuser: This is filled automatically from the |authtoken|.
1647 1647 :type apiuser: AuthUser
1648 1648 :param repoid: Set the repository name or repository ID.
1649 1649 :type repoid: str or int
1650 1650 :param usergroupid: Specify the ID of the user group.
1651 1651 :type usergroupid: str or int
1652 1652 :param perm: Set the user group permissions using the following
1653 1653 format: (repository.(none|read|write|admin))
1654 1654 :type perm: str
1655 1655
1656 1656 Example output:
1657 1657
1658 1658 .. code-block:: bash
1659 1659
1660 1660 id : <id_given_in_input>
1661 1661 result : {
1662 1662 "msg" : "Granted perm: `<perm>` for group: `<usersgroupname>` in repo: `<reponame>`",
1663 1663 "success": true
1664 1664
1665 1665 }
1666 1666 error : null
1667 1667
1668 1668 Example error output:
1669 1669
1670 1670 .. code-block:: bash
1671 1671
1672 1672 id : <id_given_in_input>
1673 1673 result : null
1674 1674 error : {
1675 1675 "failed to edit permission for user group: `<usergroup>` in repo `<repo>`'
1676 1676 }
1677 1677
1678 1678 """
1679 1679
1680 1680 repo = get_repo_or_error(repoid)
1681 1681 perm = get_perm_or_error(perm)
1682 1682 if not has_superadmin_permission(apiuser):
1683 1683 _perms = ('repository.admin',)
1684 1684 validate_repo_permissions(apiuser, repoid, repo, _perms)
1685 1685
1686 1686 user_group = get_user_group_or_error(usergroupid)
1687 1687 if not has_superadmin_permission(apiuser):
1688 1688 # check if we have at least read permission for this user group !
1689 1689 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1690 1690 if not HasUserGroupPermissionAnyApi(*_perms)(
1691 1691 user=apiuser, user_group_name=user_group.users_group_name):
1692 1692 raise JSONRPCError(
1693 1693 'user group `%s` does not exist' % (usergroupid,))
1694 1694
1695 1695 perm_additions = [[user_group.users_group_id, perm.permission_name, "user_group"]]
1696 1696 try:
1697 1697 changes = RepoModel().update_permissions(
1698 1698 repo=repo, perm_additions=perm_additions, cur_user=apiuser)
1699 1699 action_data = {
1700 1700 'added': changes['added'],
1701 1701 'updated': changes['updated'],
1702 1702 'deleted': changes['deleted'],
1703 1703 }
1704 1704 audit_logger.store_api(
1705 1705 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1706 1706
1707 1707 Session().commit()
1708 1708 return {
1709 1709 'msg': 'Granted perm: `%s` for user group: `%s` in '
1710 1710 'repo: `%s`' % (
1711 1711 perm.permission_name, user_group.users_group_name,
1712 1712 repo.repo_name
1713 1713 ),
1714 1714 'success': True
1715 1715 }
1716 1716 except Exception:
1717 1717 log.exception(
1718 1718 "Exception occurred while trying change permission on repo")
1719 1719 raise JSONRPCError(
1720 1720 'failed to edit permission for user group: `%s` in '
1721 1721 'repo: `%s`' % (
1722 1722 usergroupid, repo.repo_name
1723 1723 )
1724 1724 )
1725 1725
1726 1726
1727 1727 @jsonrpc_method()
1728 1728 def revoke_user_group_permission(request, apiuser, repoid, usergroupid):
1729 1729 """
1730 1730 Revoke the permissions of a user group on a given repository.
1731 1731
1732 1732 This command can only be run using an |authtoken| with admin
1733 1733 permissions on the |repo|.
1734 1734
1735 1735 :param apiuser: This is filled automatically from the |authtoken|.
1736 1736 :type apiuser: AuthUser
1737 1737 :param repoid: Set the repository name or repository ID.
1738 1738 :type repoid: str or int
1739 1739 :param usergroupid: Specify the user group ID.
1740 1740 :type usergroupid: str or int
1741 1741
1742 1742 Example output:
1743 1743
1744 1744 .. code-block:: bash
1745 1745
1746 1746 id : <id_given_in_input>
1747 1747 result: {
1748 1748 "msg" : "Revoked perm for group: `<usersgroupname>` in repo: `<reponame>`",
1749 1749 "success": true
1750 1750 }
1751 1751 error: null
1752 1752 """
1753 1753
1754 1754 repo = get_repo_or_error(repoid)
1755 1755 if not has_superadmin_permission(apiuser):
1756 1756 _perms = ('repository.admin',)
1757 1757 validate_repo_permissions(apiuser, repoid, repo, _perms)
1758 1758
1759 1759 user_group = get_user_group_or_error(usergroupid)
1760 1760 if not has_superadmin_permission(apiuser):
1761 1761 # check if we have at least read permission for this user group !
1762 1762 _perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin',)
1763 1763 if not HasUserGroupPermissionAnyApi(*_perms)(
1764 1764 user=apiuser, user_group_name=user_group.users_group_name):
1765 1765 raise JSONRPCError(
1766 1766 'user group `%s` does not exist' % (usergroupid,))
1767 1767
1768 1768 perm_deletions = [[user_group.users_group_id, None, "user_group"]]
1769 1769 try:
1770 1770 changes = RepoModel().update_permissions(
1771 1771 repo=repo, perm_deletions=perm_deletions, cur_user=apiuser)
1772 1772 action_data = {
1773 1773 'added': changes['added'],
1774 1774 'updated': changes['updated'],
1775 1775 'deleted': changes['deleted'],
1776 1776 }
1777 1777 audit_logger.store_api(
1778 1778 'repo.edit.permissions', action_data=action_data, user=apiuser, repo=repo)
1779 1779
1780 1780 Session().commit()
1781 1781 return {
1782 1782 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
1783 1783 user_group.users_group_name, repo.repo_name
1784 1784 ),
1785 1785 'success': True
1786 1786 }
1787 1787 except Exception:
1788 1788 log.exception("Exception occurred while trying revoke "
1789 1789 "user group permission on repo")
1790 1790 raise JSONRPCError(
1791 1791 'failed to edit permission for user group: `%s` in '
1792 1792 'repo: `%s`' % (
1793 1793 user_group.users_group_name, repo.repo_name
1794 1794 )
1795 1795 )
1796 1796
1797 1797
1798 1798 @jsonrpc_method()
1799 1799 def pull(request, apiuser, repoid, remote_uri=Optional(None)):
1800 1800 """
1801 1801 Triggers a pull on the given repository from a remote location. You
1802 1802 can use this to keep remote repositories up-to-date.
1803 1803
1804 1804 This command can only be run using an |authtoken| with admin
1805 1805 rights to the specified repository. For more information,
1806 1806 see :ref:`config-token-ref`.
1807 1807
1808 1808 This command takes the following options:
1809 1809
1810 1810 :param apiuser: This is filled automatically from the |authtoken|.
1811 1811 :type apiuser: AuthUser
1812 1812 :param repoid: The repository name or repository ID.
1813 1813 :type repoid: str or int
1814 1814 :param remote_uri: Optional remote URI to pass in for pull
1815 1815 :type remote_uri: str
1816 1816
1817 1817 Example output:
1818 1818
1819 1819 .. code-block:: bash
1820 1820
1821 1821 id : <id_given_in_input>
1822 1822 result : {
1823 1823 "msg": "Pulled from url `<remote_url>` on repo `<repository name>`"
1824 1824 "repository": "<repository name>"
1825 1825 }
1826 1826 error : null
1827 1827
1828 1828 Example error output:
1829 1829
1830 1830 .. code-block:: bash
1831 1831
1832 1832 id : <id_given_in_input>
1833 1833 result : null
1834 1834 error : {
1835 1835 "Unable to push changes from `<remote_url>`"
1836 1836 }
1837 1837
1838 1838 """
1839 1839
1840 1840 repo = get_repo_or_error(repoid)
1841 1841 remote_uri = Optional.extract(remote_uri)
1842 1842 remote_uri_display = remote_uri or repo.clone_uri_hidden
1843 1843 if not has_superadmin_permission(apiuser):
1844 1844 _perms = ('repository.admin',)
1845 1845 validate_repo_permissions(apiuser, repoid, repo, _perms)
1846 1846
1847 1847 try:
1848 1848 ScmModel().pull_changes(
1849 1849 repo.repo_name, apiuser.username, remote_uri=remote_uri)
1850 1850 return {
1851 1851 'msg': 'Pulled from url `%s` on repo `%s`' % (
1852 1852 remote_uri_display, repo.repo_name),
1853 1853 'repository': repo.repo_name
1854 1854 }
1855 1855 except Exception:
1856 1856 log.exception("Exception occurred while trying to "
1857 1857 "pull changes from remote location")
1858 1858 raise JSONRPCError(
1859 1859 'Unable to pull changes from `%s`' % remote_uri_display
1860 1860 )
1861 1861
1862 1862
1863 1863 @jsonrpc_method()
1864 1864 def strip(request, apiuser, repoid, revision, branch):
1865 1865 """
1866 1866 Strips the given revision from the specified repository.
1867 1867
1868 1868 * This will remove the revision and all of its decendants.
1869 1869
1870 1870 This command can only be run using an |authtoken| with admin rights to
1871 1871 the specified repository.
1872 1872
1873 1873 This command takes the following options:
1874 1874
1875 1875 :param apiuser: This is filled automatically from the |authtoken|.
1876 1876 :type apiuser: AuthUser
1877 1877 :param repoid: The repository name or repository ID.
1878 1878 :type repoid: str or int
1879 1879 :param revision: The revision you wish to strip.
1880 1880 :type revision: str
1881 1881 :param branch: The branch from which to strip the revision.
1882 1882 :type branch: str
1883 1883
1884 1884 Example output:
1885 1885
1886 1886 .. code-block:: bash
1887 1887
1888 1888 id : <id_given_in_input>
1889 1889 result : {
1890 1890 "msg": "'Stripped commit <commit_hash> from repo `<repository name>`'"
1891 1891 "repository": "<repository name>"
1892 1892 }
1893 1893 error : null
1894 1894
1895 1895 Example error output:
1896 1896
1897 1897 .. code-block:: bash
1898 1898
1899 1899 id : <id_given_in_input>
1900 1900 result : null
1901 1901 error : {
1902 1902 "Unable to strip commit <commit_hash> from repo `<repository name>`"
1903 1903 }
1904 1904
1905 1905 """
1906 1906
1907 1907 repo = get_repo_or_error(repoid)
1908 1908 if not has_superadmin_permission(apiuser):
1909 1909 _perms = ('repository.admin',)
1910 1910 validate_repo_permissions(apiuser, repoid, repo, _perms)
1911 1911
1912 1912 try:
1913 1913 ScmModel().strip(repo, revision, branch)
1914 1914 audit_logger.store_api(
1915 1915 'repo.commit.strip', action_data={'commit_id': revision},
1916 1916 repo=repo,
1917 1917 user=apiuser, commit=True)
1918 1918
1919 1919 return {
1920 1920 'msg': 'Stripped commit %s from repo `%s`' % (
1921 1921 revision, repo.repo_name),
1922 1922 'repository': repo.repo_name
1923 1923 }
1924 1924 except Exception:
1925 1925 log.exception("Exception while trying to strip")
1926 1926 raise JSONRPCError(
1927 1927 'Unable to strip commit %s from repo `%s`' % (
1928 1928 revision, repo.repo_name)
1929 1929 )
1930 1930
1931 1931
1932 1932 @jsonrpc_method()
1933 1933 def get_repo_settings(request, apiuser, repoid, key=Optional(None)):
1934 1934 """
1935 1935 Returns all settings for a repository. If key is given it only returns the
1936 1936 setting identified by the key or null.
1937 1937
1938 1938 :param apiuser: This is filled automatically from the |authtoken|.
1939 1939 :type apiuser: AuthUser
1940 1940 :param repoid: The repository name or repository id.
1941 1941 :type repoid: str or int
1942 1942 :param key: Key of the setting to return.
1943 1943 :type: key: Optional(str)
1944 1944
1945 1945 Example output:
1946 1946
1947 1947 .. code-block:: bash
1948 1948
1949 1949 {
1950 1950 "error": null,
1951 1951 "id": 237,
1952 1952 "result": {
1953 1953 "extensions_largefiles": true,
1954 1954 "extensions_evolve": true,
1955 1955 "hooks_changegroup_push_logger": true,
1956 1956 "hooks_changegroup_repo_size": false,
1957 1957 "hooks_outgoing_pull_logger": true,
1958 1958 "phases_publish": "True",
1959 1959 "rhodecode_hg_use_rebase_for_merging": true,
1960 1960 "rhodecode_pr_merge_enabled": true,
1961 1961 "rhodecode_use_outdated_comments": true
1962 1962 }
1963 1963 }
1964 1964 """
1965 1965
1966 1966 # Restrict access to this api method to admins only.
1967 1967 if not has_superadmin_permission(apiuser):
1968 1968 raise JSONRPCForbidden()
1969 1969
1970 1970 try:
1971 1971 repo = get_repo_or_error(repoid)
1972 1972 settings_model = VcsSettingsModel(repo=repo)
1973 1973 settings = settings_model.get_global_settings()
1974 1974 settings.update(settings_model.get_repo_settings())
1975 1975
1976 1976 # If only a single setting is requested fetch it from all settings.
1977 1977 key = Optional.extract(key)
1978 1978 if key is not None:
1979 1979 settings = settings.get(key, None)
1980 1980 except Exception:
1981 1981 msg = 'Failed to fetch settings for repository `{}`'.format(repoid)
1982 1982 log.exception(msg)
1983 1983 raise JSONRPCError(msg)
1984 1984
1985 1985 return settings
1986 1986
1987 1987
1988 1988 @jsonrpc_method()
1989 1989 def set_repo_settings(request, apiuser, repoid, settings):
1990 1990 """
1991 1991 Update repository settings. Returns true on success.
1992 1992
1993 1993 :param apiuser: This is filled automatically from the |authtoken|.
1994 1994 :type apiuser: AuthUser
1995 1995 :param repoid: The repository name or repository id.
1996 1996 :type repoid: str or int
1997 1997 :param settings: The new settings for the repository.
1998 1998 :type: settings: dict
1999 1999
2000 2000 Example output:
2001 2001
2002 2002 .. code-block:: bash
2003 2003
2004 2004 {
2005 2005 "error": null,
2006 2006 "id": 237,
2007 2007 "result": true
2008 2008 }
2009 2009 """
2010 2010 # Restrict access to this api method to admins only.
2011 2011 if not has_superadmin_permission(apiuser):
2012 2012 raise JSONRPCForbidden()
2013 2013
2014 2014 if type(settings) is not dict:
2015 2015 raise JSONRPCError('Settings have to be a JSON Object.')
2016 2016
2017 2017 try:
2018 2018 settings_model = VcsSettingsModel(repo=repoid)
2019 2019
2020 2020 # Merge global, repo and incoming settings.
2021 2021 new_settings = settings_model.get_global_settings()
2022 2022 new_settings.update(settings_model.get_repo_settings())
2023 2023 new_settings.update(settings)
2024 2024
2025 2025 # Update the settings.
2026 2026 inherit_global_settings = new_settings.get(
2027 2027 'inherit_global_settings', False)
2028 2028 settings_model.create_or_update_repo_settings(
2029 2029 new_settings, inherit_global_settings=inherit_global_settings)
2030 2030 Session().commit()
2031 2031 except Exception:
2032 2032 msg = 'Failed to update settings for repository `{}`'.format(repoid)
2033 2033 log.exception(msg)
2034 2034 raise JSONRPCError(msg)
2035 2035
2036 2036 # Indicate success.
2037 2037 return True
2038 2038
2039 2039
2040 2040 @jsonrpc_method()
2041 2041 def maintenance(request, apiuser, repoid):
2042 2042 """
2043 2043 Triggers a maintenance on the given repository.
2044 2044
2045 2045 This command can only be run using an |authtoken| with admin
2046 2046 rights to the specified repository. For more information,
2047 2047 see :ref:`config-token-ref`.
2048 2048
2049 2049 This command takes the following options:
2050 2050
2051 2051 :param apiuser: This is filled automatically from the |authtoken|.
2052 2052 :type apiuser: AuthUser
2053 2053 :param repoid: The repository name or repository ID.
2054 2054 :type repoid: str or int
2055 2055
2056 2056 Example output:
2057 2057
2058 2058 .. code-block:: bash
2059 2059
2060 2060 id : <id_given_in_input>
2061 2061 result : {
2062 2062 "msg": "executed maintenance command",
2063 2063 "executed_actions": [
2064 2064 <action_message>, <action_message2>...
2065 2065 ],
2066 2066 "repository": "<repository name>"
2067 2067 }
2068 2068 error : null
2069 2069
2070 2070 Example error output:
2071 2071
2072 2072 .. code-block:: bash
2073 2073
2074 2074 id : <id_given_in_input>
2075 2075 result : null
2076 2076 error : {
2077 2077 "Unable to execute maintenance on `<reponame>`"
2078 2078 }
2079 2079
2080 2080 """
2081 2081
2082 2082 repo = get_repo_or_error(repoid)
2083 2083 if not has_superadmin_permission(apiuser):
2084 2084 _perms = ('repository.admin',)
2085 2085 validate_repo_permissions(apiuser, repoid, repo, _perms)
2086 2086
2087 2087 try:
2088 2088 maintenance = repo_maintenance.RepoMaintenance()
2089 2089 executed_actions = maintenance.execute(repo)
2090 2090
2091 2091 return {
2092 2092 'msg': 'executed maintenance command',
2093 2093 'executed_actions': executed_actions,
2094 2094 'repository': repo.repo_name
2095 2095 }
2096 2096 except Exception:
2097 2097 log.exception("Exception occurred while trying to run maintenance")
2098 2098 raise JSONRPCError(
2099 2099 'Unable to execute maintenance on `%s`' % repo.repo_name)
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was 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