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

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

@@ -1,57 +1,57 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 sys
23 23 import platform
24 24
25 25 VERSION = tuple(open(os.path.join(
26 26 os.path.dirname(__file__), 'VERSION')).read().split('.'))
27 27
28 28 BACKENDS = {
29 29 'hg': 'Mercurial repository',
30 30 'git': 'Git repository',
31 31 'svn': 'Subversion repository',
32 32 }
33 33
34 34 CELERY_ENABLED = False
35 35 CELERY_EAGER = False
36 36
37 37 # link to config for pyramid
38 38 CONFIG = {}
39 39
40 40 # Populated with the settings dictionary from application init in
41 41 # rhodecode.conf.environment.load_pyramid_environment
42 42 PYRAMID_SETTINGS = {}
43 43
44 44 # Linked module for extensions
45 45 EXTENSIONS = {}
46 46
47 47 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
48 48 __dbversion__ = 105 # defines current db version for migrations
49 49 __platform__ = platform.system()
50 50 __license__ = 'AGPLv3, and Commercial License'
51 51 __author__ = 'RhodeCode GmbH'
52 52 __url__ = 'https://code.rhodecode.com'
53 53
54 54 is_windows = __platform__ in ['Windows']
55 55 is_unix = not is_windows
56 56 is_test = False
57 57 disable_error_handler = False
@@ -1,555 +1,555 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import itertools
22 22 import logging
23 23 import sys
24 24 import types
25 25 import fnmatch
26 26
27 27 import decorator
28 28 import venusian
29 29 from collections import OrderedDict
30 30
31 31 from pyramid.exceptions import ConfigurationError
32 32 from pyramid.renderers import render
33 33 from pyramid.response import Response
34 34 from pyramid.httpexceptions import HTTPNotFound
35 35
36 36 from rhodecode.api.exc import (
37 37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 38 from rhodecode.apps._base import TemplateArgs
39 39 from rhodecode.lib.auth import AuthUser
40 40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
41 41 from rhodecode.lib.exc_tracking import store_exception
42 42 from rhodecode.lib.ext_json import json
43 43 from rhodecode.lib.utils2 import safe_str
44 44 from rhodecode.lib.plugins.utils import get_plugin_settings
45 45 from rhodecode.model.db import User, UserApiKeys
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49 DEFAULT_RENDERER = 'jsonrpc_renderer'
50 50 DEFAULT_URL = '/_admin/apiv2'
51 51
52 52
53 53 def find_methods(jsonrpc_methods, pattern):
54 54 matches = OrderedDict()
55 55 if not isinstance(pattern, (list, tuple)):
56 56 pattern = [pattern]
57 57
58 58 for single_pattern in pattern:
59 59 for method_name, method in jsonrpc_methods.items():
60 60 if fnmatch.fnmatch(method_name, single_pattern):
61 61 matches[method_name] = method
62 62 return matches
63 63
64 64
65 65 class ExtJsonRenderer(object):
66 66 """
67 67 Custom renderer that mkaes use of our ext_json lib
68 68
69 69 """
70 70
71 71 def __init__(self, serializer=json.dumps, **kw):
72 72 """ Any keyword arguments will be passed to the ``serializer``
73 73 function."""
74 74 self.serializer = serializer
75 75 self.kw = kw
76 76
77 77 def __call__(self, info):
78 78 """ Returns a plain JSON-encoded string with content-type
79 79 ``application/json``. The content-type may be overridden by
80 80 setting ``request.response.content_type``."""
81 81
82 82 def _render(value, system):
83 83 request = system.get('request')
84 84 if request is not None:
85 85 response = request.response
86 86 ct = response.content_type
87 87 if ct == response.default_content_type:
88 88 response.content_type = 'application/json'
89 89
90 90 return self.serializer(value, **self.kw)
91 91
92 92 return _render
93 93
94 94
95 95 def jsonrpc_response(request, result):
96 96 rpc_id = getattr(request, 'rpc_id', None)
97 97 response = request.response
98 98
99 99 # store content_type before render is called
100 100 ct = response.content_type
101 101
102 102 ret_value = ''
103 103 if rpc_id:
104 104 ret_value = {
105 105 'id': rpc_id,
106 106 'result': result,
107 107 'error': None,
108 108 }
109 109
110 110 # fetch deprecation warnings, and store it inside results
111 111 deprecation = getattr(request, 'rpc_deprecation', None)
112 112 if deprecation:
113 113 ret_value['DEPRECATION_WARNING'] = deprecation
114 114
115 115 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
116 116 response.body = safe_str(raw_body, response.charset)
117 117
118 118 if ct == response.default_content_type:
119 119 response.content_type = 'application/json'
120 120
121 121 return response
122 122
123 123
124 124 def jsonrpc_error(request, message, retid=None, code=None, headers=None):
125 125 """
126 126 Generate a Response object with a JSON-RPC error body
127 127
128 128 :param code:
129 129 :param retid:
130 130 :param message:
131 131 """
132 132 err_dict = {'id': retid, 'result': None, 'error': message}
133 133 body = render(DEFAULT_RENDERER, err_dict, request=request).encode('utf-8')
134 134
135 135 return Response(
136 136 body=body,
137 137 status=code,
138 138 content_type='application/json',
139 139 headerlist=headers
140 140 )
141 141
142 142
143 143 def exception_view(exc, request):
144 144 rpc_id = getattr(request, 'rpc_id', None)
145 145
146 146 if isinstance(exc, JSONRPCError):
147 147 fault_message = safe_str(exc.message)
148 148 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
149 149 elif isinstance(exc, JSONRPCValidationError):
150 150 colander_exc = exc.colander_exception
151 151 # TODO(marcink): think maybe of nicer way to serialize errors ?
152 152 fault_message = colander_exc.asdict()
153 153 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
154 154 elif isinstance(exc, JSONRPCForbidden):
155 155 fault_message = 'Access was denied to this resource.'
156 156 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
157 157 elif isinstance(exc, HTTPNotFound):
158 158 method = request.rpc_method
159 159 log.debug('json-rpc method `%s` not found in list of '
160 160 'api calls: %s, rpc_id:%s',
161 161 method, request.registry.jsonrpc_methods.keys(), rpc_id)
162 162
163 163 similar = 'none'
164 164 try:
165 165 similar_paterns = ['*{}*'.format(x) for x in method.split('_')]
166 166 similar_found = find_methods(
167 167 request.registry.jsonrpc_methods, similar_paterns)
168 168 similar = ', '.join(similar_found.keys()) or similar
169 169 except Exception:
170 170 # make the whole above block safe
171 171 pass
172 172
173 173 fault_message = "No such method: {}. Similar methods: {}".format(
174 174 method, similar)
175 175 else:
176 176 fault_message = 'undefined error'
177 177 exc_info = exc.exc_info()
178 178 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
179 179
180 180 return jsonrpc_error(request, fault_message, rpc_id)
181 181
182 182
183 183 def request_view(request):
184 184 """
185 185 Main request handling method. It handles all logic to call a specific
186 186 exposed method
187 187 """
188 188 # cython compatible inspect
189 189 from rhodecode.config.patches import inspect_getargspec
190 190 inspect = inspect_getargspec()
191 191
192 192 # check if we can find this session using api_key, get_by_auth_token
193 193 # search not expired tokens only
194 194 try:
195 195 api_user = User.get_by_auth_token(request.rpc_api_key)
196 196
197 197 if api_user is None:
198 198 return jsonrpc_error(
199 199 request, retid=request.rpc_id, message='Invalid API KEY')
200 200
201 201 if not api_user.active:
202 202 return jsonrpc_error(
203 203 request, retid=request.rpc_id,
204 204 message='Request from this user not allowed')
205 205
206 206 # check if we are allowed to use this IP
207 207 auth_u = AuthUser(
208 208 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
209 209 if not auth_u.ip_allowed:
210 210 return jsonrpc_error(
211 211 request, retid=request.rpc_id,
212 212 message='Request from IP:%s not allowed' % (
213 213 request.rpc_ip_addr,))
214 214 else:
215 215 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
216 216
217 217 # register our auth-user
218 218 request.rpc_user = auth_u
219 219 request.environ['rc_auth_user_id'] = auth_u.user_id
220 220
221 221 # now check if token is valid for API
222 222 auth_token = request.rpc_api_key
223 223 token_match = api_user.authenticate_by_token(
224 224 auth_token, roles=[UserApiKeys.ROLE_API])
225 225 invalid_token = not token_match
226 226
227 227 log.debug('Checking if API KEY is valid with proper role')
228 228 if invalid_token:
229 229 return jsonrpc_error(
230 230 request, retid=request.rpc_id,
231 231 message='API KEY invalid or, has bad role for an API call')
232 232
233 233 except Exception:
234 234 log.exception('Error on API AUTH')
235 235 return jsonrpc_error(
236 236 request, retid=request.rpc_id, message='Invalid API KEY')
237 237
238 238 method = request.rpc_method
239 239 func = request.registry.jsonrpc_methods[method]
240 240
241 241 # now that we have a method, add request._req_params to
242 242 # self.kargs and dispatch control to WGIController
243 243 argspec = inspect.getargspec(func)
244 244 arglist = argspec[0]
245 245 defaults = map(type, argspec[3] or [])
246 246 default_empty = types.NotImplementedType
247 247
248 248 # kw arguments required by this method
249 249 func_kwargs = dict(itertools.izip_longest(
250 250 reversed(arglist), reversed(defaults), fillvalue=default_empty))
251 251
252 252 # This attribute will need to be first param of a method that uses
253 253 # api_key, which is translated to instance of user at that name
254 254 user_var = 'apiuser'
255 255 request_var = 'request'
256 256
257 257 for arg in [user_var, request_var]:
258 258 if arg not in arglist:
259 259 return jsonrpc_error(
260 260 request,
261 261 retid=request.rpc_id,
262 262 message='This method [%s] does not support '
263 263 'required parameter `%s`' % (func.__name__, arg))
264 264
265 265 # get our arglist and check if we provided them as args
266 266 for arg, default in func_kwargs.items():
267 267 if arg in [user_var, request_var]:
268 268 # user_var and request_var are pre-hardcoded parameters and we
269 269 # don't need to do any translation
270 270 continue
271 271
272 272 # skip the required param check if it's default value is
273 273 # NotImplementedType (default_empty)
274 274 if default == default_empty and arg not in request.rpc_params:
275 275 return jsonrpc_error(
276 276 request,
277 277 retid=request.rpc_id,
278 278 message=('Missing non optional `%s` arg in JSON DATA' % arg)
279 279 )
280 280
281 281 # sanitize extra passed arguments
282 282 for k in request.rpc_params.keys()[:]:
283 283 if k not in func_kwargs:
284 284 del request.rpc_params[k]
285 285
286 286 call_params = request.rpc_params
287 287 call_params.update({
288 288 'request': request,
289 289 'apiuser': auth_u
290 290 })
291 291
292 292 # register some common functions for usage
293 293 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
294 294
295 295 try:
296 296 ret_value = func(**call_params)
297 297 return jsonrpc_response(request, ret_value)
298 298 except JSONRPCBaseError:
299 299 raise
300 300 except Exception:
301 301 log.exception('Unhandled exception occurred on api call: %s', func)
302 302 exc_info = sys.exc_info()
303 303 exc_id, exc_type_name = store_exception(
304 304 id(exc_info), exc_info, prefix='rhodecode-api')
305 305 error_headers = [('RhodeCode-Exception-Id', str(exc_id)),
306 306 ('RhodeCode-Exception-Type', str(exc_type_name))]
307 307 return jsonrpc_error(
308 308 request, retid=request.rpc_id, message='Internal server error',
309 309 headers=error_headers)
310 310
311 311
312 312 def setup_request(request):
313 313 """
314 314 Parse a JSON-RPC request body. It's used inside the predicates method
315 315 to validate and bootstrap requests for usage in rpc calls.
316 316
317 317 We need to raise JSONRPCError here if we want to return some errors back to
318 318 user.
319 319 """
320 320
321 321 log.debug('Executing setup request: %r', request)
322 322 request.rpc_ip_addr = get_ip_addr(request.environ)
323 323 # TODO(marcink): deprecate GET at some point
324 324 if request.method not in ['POST', 'GET']:
325 325 log.debug('unsupported request method "%s"', request.method)
326 326 raise JSONRPCError(
327 327 'unsupported request method "%s". Please use POST' % request.method)
328 328
329 329 if 'CONTENT_LENGTH' not in request.environ:
330 330 log.debug("No Content-Length")
331 331 raise JSONRPCError("Empty body, No Content-Length in request")
332 332
333 333 else:
334 334 length = request.environ['CONTENT_LENGTH']
335 335 log.debug('Content-Length: %s', length)
336 336
337 337 if length == 0:
338 338 log.debug("Content-Length is 0")
339 339 raise JSONRPCError("Content-Length is 0")
340 340
341 341 raw_body = request.body
342 342 log.debug("Loading JSON body now")
343 343 try:
344 344 json_body = json.loads(raw_body)
345 345 except ValueError as e:
346 346 # catch JSON errors Here
347 347 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
348 348
349 349 request.rpc_id = json_body.get('id')
350 350 request.rpc_method = json_body.get('method')
351 351
352 352 # check required base parameters
353 353 try:
354 354 api_key = json_body.get('api_key')
355 355 if not api_key:
356 356 api_key = json_body.get('auth_token')
357 357
358 358 if not api_key:
359 359 raise KeyError('api_key or auth_token')
360 360
361 361 # TODO(marcink): support passing in token in request header
362 362
363 363 request.rpc_api_key = api_key
364 364 request.rpc_id = json_body['id']
365 365 request.rpc_method = json_body['method']
366 366 request.rpc_params = json_body['args'] \
367 367 if isinstance(json_body['args'], dict) else {}
368 368
369 369 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
370 370 except KeyError as e:
371 371 raise JSONRPCError('Incorrect JSON data. Missing %s' % e)
372 372
373 373 log.debug('setup complete, now handling method:%s rpcid:%s',
374 374 request.rpc_method, request.rpc_id, )
375 375
376 376
377 377 class RoutePredicate(object):
378 378 def __init__(self, val, config):
379 379 self.val = val
380 380
381 381 def text(self):
382 382 return 'jsonrpc route = %s' % self.val
383 383
384 384 phash = text
385 385
386 386 def __call__(self, info, request):
387 387 if self.val:
388 388 # potentially setup and bootstrap our call
389 389 setup_request(request)
390 390
391 391 # Always return True so that even if it isn't a valid RPC it
392 392 # will fall through to the underlaying handlers like notfound_view
393 393 return True
394 394
395 395
396 396 class NotFoundPredicate(object):
397 397 def __init__(self, val, config):
398 398 self.val = val
399 399 self.methods = config.registry.jsonrpc_methods
400 400
401 401 def text(self):
402 402 return 'jsonrpc method not found = {}.'.format(self.val)
403 403
404 404 phash = text
405 405
406 406 def __call__(self, info, request):
407 407 return hasattr(request, 'rpc_method')
408 408
409 409
410 410 class MethodPredicate(object):
411 411 def __init__(self, val, config):
412 412 self.method = val
413 413
414 414 def text(self):
415 415 return 'jsonrpc method = %s' % self.method
416 416
417 417 phash = text
418 418
419 419 def __call__(self, context, request):
420 420 # we need to explicitly return False here, so pyramid doesn't try to
421 421 # execute our view directly. We need our main handler to execute things
422 422 return getattr(request, 'rpc_method') == self.method
423 423
424 424
425 425 def add_jsonrpc_method(config, view, **kwargs):
426 426 # pop the method name
427 427 method = kwargs.pop('method', None)
428 428
429 429 if method is None:
430 430 raise ConfigurationError(
431 431 'Cannot register a JSON-RPC method without specifying the "method"')
432 432
433 433 # we define custom predicate, to enable to detect conflicting methods,
434 434 # those predicates are kind of "translation" from the decorator variables
435 435 # to internal predicates names
436 436
437 437 kwargs['jsonrpc_method'] = method
438 438
439 439 # register our view into global view store for validation
440 440 config.registry.jsonrpc_methods[method] = view
441 441
442 442 # we're using our main request_view handler, here, so each method
443 443 # has a unified handler for itself
444 444 config.add_view(request_view, route_name='apiv2', **kwargs)
445 445
446 446
447 447 class jsonrpc_method(object):
448 448 """
449 449 decorator that works similar to @add_view_config decorator,
450 450 but tailored for our JSON RPC
451 451 """
452 452
453 453 venusian = venusian # for testing injection
454 454
455 455 def __init__(self, method=None, **kwargs):
456 456 self.method = method
457 457 self.kwargs = kwargs
458 458
459 459 def __call__(self, wrapped):
460 460 kwargs = self.kwargs.copy()
461 461 kwargs['method'] = self.method or wrapped.__name__
462 462 depth = kwargs.pop('_depth', 0)
463 463
464 464 def callback(context, name, ob):
465 465 config = context.config.with_package(info.module)
466 466 config.add_jsonrpc_method(view=ob, **kwargs)
467 467
468 468 info = venusian.attach(wrapped, callback, category='pyramid',
469 469 depth=depth + 1)
470 470 if info.scope == 'class':
471 471 # ensure that attr is set if decorating a class method
472 472 kwargs.setdefault('attr', wrapped.__name__)
473 473
474 474 kwargs['_info'] = info.codeinfo # fbo action_method
475 475 return wrapped
476 476
477 477
478 478 class jsonrpc_deprecated_method(object):
479 479 """
480 480 Marks method as deprecated, adds log.warning, and inject special key to
481 481 the request variable to mark method as deprecated.
482 482 Also injects special docstring that extract_docs will catch to mark
483 483 method as deprecated.
484 484
485 485 :param use_method: specify which method should be used instead of
486 486 the decorated one
487 487
488 488 Use like::
489 489
490 490 @jsonrpc_method()
491 491 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
492 492 def old_func(request, apiuser, arg1, arg2):
493 493 ...
494 494 """
495 495
496 496 def __init__(self, use_method, deprecated_at_version):
497 497 self.use_method = use_method
498 498 self.deprecated_at_version = deprecated_at_version
499 499 self.deprecated_msg = ''
500 500
501 501 def __call__(self, func):
502 502 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
503 503 method=self.use_method)
504 504
505 505 docstring = """\n
506 506 .. deprecated:: {version}
507 507
508 508 {deprecation_message}
509 509
510 510 {original_docstring}
511 511 """
512 512 func.__doc__ = docstring.format(
513 513 version=self.deprecated_at_version,
514 514 deprecation_message=self.deprecated_msg,
515 515 original_docstring=func.__doc__)
516 516 return decorator.decorator(self.__wrapper, func)
517 517
518 518 def __wrapper(self, func, *fargs, **fkwargs):
519 519 log.warning('DEPRECATED API CALL on function %s, please '
520 520 'use `%s` instead', func, self.use_method)
521 521 # alter function docstring to mark as deprecated, this is picked up
522 522 # via fabric file that generates API DOC.
523 523 result = func(*fargs, **fkwargs)
524 524
525 525 request = fargs[0]
526 526 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
527 527 return result
528 528
529 529
530 530 def includeme(config):
531 531 plugin_module = 'rhodecode.api'
532 532 plugin_settings = get_plugin_settings(
533 533 plugin_module, config.registry.settings)
534 534
535 535 if not hasattr(config.registry, 'jsonrpc_methods'):
536 536 config.registry.jsonrpc_methods = OrderedDict()
537 537
538 538 # match filter by given method only
539 539 config.add_view_predicate('jsonrpc_method', MethodPredicate)
540 540 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
541 541
542 542 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer(
543 543 serializer=json.dumps, indent=4))
544 544 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
545 545
546 546 config.add_route_predicate(
547 547 'jsonrpc_call', RoutePredicate)
548 548
549 549 config.add_route(
550 550 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
551 551
552 552 config.scan(plugin_module, ignore='rhodecode.api.tests')
553 553 # register some exception handling view
554 554 config.add_view(exception_view, context=JSONRPCBaseError)
555 555 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-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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,133 +1,133 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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. ' \
90 90 'Similar methods: changeset_comment, comment_pull_request, ' \
91 91 'get_pull_request_comments, comment_commit, get_repo_comments'
92 92 assert_error(id_, expected, given=response.body)
93 93
94 94 def test_api_disabled_user(self, request):
95 95
96 96 def set_active(active):
97 97 from rhodecode.model.db import Session, User
98 98 user = User.get_by_auth_token(self.apikey)
99 99 user.active = active
100 100 Session().add(user)
101 101 Session().commit()
102 102
103 103 request.addfinalizer(lambda: set_active(True))
104 104
105 105 set_active(False)
106 106 id_, params = build_data(self.apikey, 'test', args='xx')
107 107 response = api_call(self.app, params)
108 108 expected = 'Request from this user not allowed'
109 109 assert_error(id_, expected, given=response.body)
110 110
111 111 def test_api_args_is_null(self):
112 112 __, params = build_data(self.apikey, 'get_users', )
113 113 params = params.replace('"args": {}', '"args": null')
114 114 response = api_call(self.app, params)
115 115 assert response.status == '200 OK'
116 116
117 117 def test_api_args_is_bad(self):
118 118 __, params = build_data(self.apikey, 'get_users', )
119 119 params = params.replace('"args": {}', '"args": 1')
120 120 response = api_call(self.app, params)
121 121 assert response.status == '200 OK'
122 122
123 123 def test_api_args_different_args(self):
124 124 import string
125 125 expected = {
126 126 'ascii_letters': string.ascii_letters,
127 127 'ws': string.whitespace,
128 128 'printables': string.printable
129 129 }
130 130 id_, params = build_data(self.apikey, 'test', args=expected)
131 131 response = api_call(self.app, params)
132 132 assert response.status == '200 OK'
133 133 assert_ok(id_, expected, response.body)
@@ -1,44 +1,44 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2017-2019 RhodeCode GmbH
3 # Copyright (C) 2017-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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(UserLog.user_log_id.asc()) \
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,116 +1,116 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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, User
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().name, 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)
82 82
83 83 def test_api_comment_commit_with_extra_recipients(self, backend, user_util):
84 84
85 85 commit_id = backend.repo.scm_instance().get_commit('tip').raw_id
86 86
87 87 user1 = user_util.create_user()
88 88 user1_id = user1.user_id
89 89 user2 = user_util.create_user()
90 90 user2_id = user2.user_id
91 91
92 92 id_, params = build_data(
93 93 self.apikey, 'comment_commit', repoid=backend.repo_name,
94 94 commit_id=commit_id,
95 95 message='abracadabra',
96 96 extra_recipients=[user1.user_id, user2.username])
97 97
98 98 response = api_call(self.app, params)
99 99 repo = backend.repo.scm_instance()
100 100
101 101 expected = {
102 102 'msg': 'Commented on commit `%s` for repository `%s`' % (
103 103 repo.get_commit().raw_id, backend.repo_name),
104 104 'status_change': None,
105 105 'success': True
106 106 }
107 107
108 108 assert_ok(id_, expected, given=response.body)
109 109 # check user1/user2 inbox for notification
110 110 user1 = User.get(user1_id)
111 111 assert 1 == len(user1.notifications)
112 112 assert 'abracadabra' in user1.notifications[0].notification.body
113 113
114 114 user2 = User.get(user2_id)
115 115 assert 1 == len(user2.notifications)
116 116 assert 'abracadabra' in user2.notifications[0].notification.body
@@ -1,246 +1,246 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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, 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_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(UserLog.user_log_id.asc()) \
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_with_extra_recipients(self, pr_util, user_util):
74 74 pull_request = pr_util.create_pull_request()
75 75
76 76 user1 = user_util.create_user()
77 77 user1_id = user1.user_id
78 78 user2 = user_util.create_user()
79 79 user2_id = user2.user_id
80 80
81 81 id_, params = build_data(
82 82 self.apikey, 'comment_pull_request',
83 83 repoid=pull_request.target_repo.repo_name,
84 84 pullrequestid=pull_request.pull_request_id,
85 85 message='test message',
86 86 extra_recipients=[user1.user_id, user2.username]
87 87 )
88 88 response = api_call(self.app, params)
89 89 pull_request = PullRequestModel().get(pull_request.pull_request_id)
90 90
91 91 comments = CommentsModel().get_comments(
92 92 pull_request.target_repo.repo_id, pull_request=pull_request)
93 93
94 94 expected = {
95 95 'pull_request_id': pull_request.pull_request_id,
96 96 'comment_id': comments[-1].comment_id,
97 97 'status': {'given': None, 'was_changed': None}
98 98 }
99 99 assert_ok(id_, expected, response.body)
100 100 # check user1/user2 inbox for notification
101 101 user1 = User.get(user1_id)
102 102 assert 1 == len(user1.notifications)
103 103 assert 'test message' in user1.notifications[0].notification.body
104 104
105 105 user2 = User.get(user2_id)
106 106 assert 1 == len(user2.notifications)
107 107 assert 'test message' in user2.notifications[0].notification.body
108 108
109 109 @pytest.mark.backends("git", "hg")
110 110 def test_api_comment_pull_request_change_status(
111 111 self, pr_util, no_notifications):
112 112 pull_request = pr_util.create_pull_request()
113 113 pull_request_id = pull_request.pull_request_id
114 114 id_, params = build_data(
115 115 self.apikey, 'comment_pull_request',
116 116 repoid=pull_request.target_repo.repo_name,
117 117 pullrequestid=pull_request.pull_request_id,
118 118 status='rejected')
119 119 response = api_call(self.app, params)
120 120 pull_request = PullRequestModel().get(pull_request_id)
121 121
122 122 comments = CommentsModel().get_comments(
123 123 pull_request.target_repo.repo_id, pull_request=pull_request)
124 124 expected = {
125 125 'pull_request_id': pull_request.pull_request_id,
126 126 'comment_id': comments[-1].comment_id,
127 127 'status': {'given': 'rejected', 'was_changed': True}
128 128 }
129 129 assert_ok(id_, expected, response.body)
130 130
131 131 @pytest.mark.backends("git", "hg")
132 132 def test_api_comment_pull_request_change_status_with_specific_commit_id(
133 133 self, pr_util, no_notifications):
134 134 pull_request = pr_util.create_pull_request()
135 135 pull_request_id = pull_request.pull_request_id
136 136 latest_commit_id = 'test_commit'
137 137 # inject additional revision, to fail test the status change on
138 138 # non-latest commit
139 139 pull_request.revisions = pull_request.revisions + ['test_commit']
140 140
141 141 id_, params = build_data(
142 142 self.apikey, 'comment_pull_request',
143 143 repoid=pull_request.target_repo.repo_name,
144 144 pullrequestid=pull_request.pull_request_id,
145 145 status='approved', commit_id=latest_commit_id)
146 146 response = api_call(self.app, params)
147 147 pull_request = PullRequestModel().get(pull_request_id)
148 148
149 149 expected = {
150 150 'pull_request_id': pull_request.pull_request_id,
151 151 'comment_id': None,
152 152 'status': {'given': 'approved', 'was_changed': False}
153 153 }
154 154 assert_ok(id_, expected, response.body)
155 155
156 156 @pytest.mark.backends("git", "hg")
157 157 def test_api_comment_pull_request_change_status_with_specific_commit_id(
158 158 self, pr_util, no_notifications):
159 159 pull_request = pr_util.create_pull_request()
160 160 pull_request_id = pull_request.pull_request_id
161 161 latest_commit_id = pull_request.revisions[0]
162 162
163 163 id_, params = build_data(
164 164 self.apikey, 'comment_pull_request',
165 165 repoid=pull_request.target_repo.repo_name,
166 166 pullrequestid=pull_request.pull_request_id,
167 167 status='approved', commit_id=latest_commit_id)
168 168 response = api_call(self.app, params)
169 169 pull_request = PullRequestModel().get(pull_request_id)
170 170
171 171 comments = CommentsModel().get_comments(
172 172 pull_request.target_repo.repo_id, pull_request=pull_request)
173 173 expected = {
174 174 'pull_request_id': pull_request.pull_request_id,
175 175 'comment_id': comments[-1].comment_id,
176 176 'status': {'given': 'approved', 'was_changed': True}
177 177 }
178 178 assert_ok(id_, expected, response.body)
179 179
180 180 @pytest.mark.backends("git", "hg")
181 181 def test_api_comment_pull_request_missing_params_error(self, pr_util):
182 182 pull_request = pr_util.create_pull_request()
183 183 pull_request_id = pull_request.pull_request_id
184 184 pull_request_repo = pull_request.target_repo.repo_name
185 185 id_, params = build_data(
186 186 self.apikey, 'comment_pull_request',
187 187 repoid=pull_request_repo,
188 188 pullrequestid=pull_request_id)
189 189 response = api_call(self.app, params)
190 190
191 191 expected = 'Both message and status parameters are missing. At least one is required.'
192 192 assert_error(id_, expected, given=response.body)
193 193
194 194 @pytest.mark.backends("git", "hg")
195 195 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
196 196 pull_request = pr_util.create_pull_request()
197 197 pull_request_id = pull_request.pull_request_id
198 198 pull_request_repo = pull_request.target_repo.repo_name
199 199 id_, params = build_data(
200 200 self.apikey, 'comment_pull_request',
201 201 repoid=pull_request_repo,
202 202 pullrequestid=pull_request_id,
203 203 status='42')
204 204 response = api_call(self.app, params)
205 205
206 206 expected = 'Unknown comment status: `42`'
207 207 assert_error(id_, expected, given=response.body)
208 208
209 209 @pytest.mark.backends("git", "hg")
210 210 def test_api_comment_pull_request_repo_error(self, pr_util):
211 211 pull_request = pr_util.create_pull_request()
212 212 id_, params = build_data(
213 213 self.apikey, 'comment_pull_request',
214 214 repoid=666, pullrequestid=pull_request.pull_request_id)
215 215 response = api_call(self.app, params)
216 216
217 217 expected = 'repository `666` does not exist'
218 218 assert_error(id_, expected, given=response.body)
219 219
220 220 @pytest.mark.backends("git", "hg")
221 221 def test_api_comment_pull_request_non_admin_with_userid_error(
222 222 self, pr_util):
223 223 pull_request = pr_util.create_pull_request()
224 224 id_, params = build_data(
225 225 self.apikey_regular, 'comment_pull_request',
226 226 repoid=pull_request.target_repo.repo_name,
227 227 pullrequestid=pull_request.pull_request_id,
228 228 userid=TEST_USER_ADMIN_LOGIN)
229 229 response = api_call(self.app, params)
230 230
231 231 expected = 'userid is not the same as your user'
232 232 assert_error(id_, expected, given=response.body)
233 233
234 234 @pytest.mark.backends("git", "hg")
235 235 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
236 236 pull_request = pr_util.create_pull_request()
237 237 id_, params = build_data(
238 238 self.apikey_regular, 'comment_pull_request',
239 239 repoid=pull_request.target_repo.repo_name,
240 240 status='approved',
241 241 pullrequestid=pull_request.pull_request_id,
242 242 commit_id='XXX')
243 243 response = api_call(self.app, params)
244 244
245 245 expected = 'Invalid commit_id `XXX` for this pull request.'
246 246 assert_error(id_, expected, given=response.body)
@@ -1,102 +1,102 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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,207 +1,207 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 description='CTO of Things',
86 86 password='example')
87 87 response = api_call(self.app, params)
88 88
89 89 usr = UserModel().get_by_username(username)
90 90 ret = {
91 91 'msg': 'created new user `%s`' % (username,),
92 92 'user': jsonify(usr.get_api_data(include_secrets=True)),
93 93 }
94 94 try:
95 95 expected = ret
96 96 assert check_password('example', usr.password)
97 97 assert_ok(id_, expected, given=response.body)
98 98 finally:
99 99 fixture.destroy_user(usr.user_id)
100 100
101 101 def test_api_create_user_without_password(self):
102 102 username = 'test_new_api_user_passwordless'
103 103 email = username + "@foo.com"
104 104
105 105 id_, params = build_data(
106 106 self.apikey, 'create_user',
107 107 username=username,
108 108 email=email)
109 109 response = api_call(self.app, params)
110 110
111 111 usr = UserModel().get_by_username(username)
112 112 ret = {
113 113 'msg': 'created new user `%s`' % (username,),
114 114 'user': jsonify(usr.get_api_data(include_secrets=True)),
115 115 }
116 116 try:
117 117 expected = ret
118 118 assert_ok(id_, expected, given=response.body)
119 119 finally:
120 120 fixture.destroy_user(usr.user_id)
121 121
122 122 def test_api_create_user_with_extern_name(self):
123 123 username = 'test_new_api_user_passwordless'
124 124 email = username + "@foo.com"
125 125
126 126 id_, params = build_data(
127 127 self.apikey, 'create_user',
128 128 username=username,
129 129 email=email, extern_name='rhodecode')
130 130 response = api_call(self.app, params)
131 131
132 132 usr = UserModel().get_by_username(username)
133 133 ret = {
134 134 'msg': 'created new user `%s`' % (username,),
135 135 'user': jsonify(usr.get_api_data(include_secrets=True)),
136 136 }
137 137 try:
138 138 expected = ret
139 139 assert_ok(id_, expected, given=response.body)
140 140 finally:
141 141 fixture.destroy_user(usr.user_id)
142 142
143 143 def test_api_create_user_with_password_change(self):
144 144 username = 'test_new_api_user_password_change'
145 145 email = username + "@foo.com"
146 146
147 147 id_, params = build_data(
148 148 self.apikey, 'create_user',
149 149 username=username,
150 150 email=email, extern_name='rhodecode',
151 151 force_password_change=True)
152 152 response = api_call(self.app, params)
153 153
154 154 usr = UserModel().get_by_username(username)
155 155 ret = {
156 156 'msg': 'created new user `%s`' % (username,),
157 157 'user': jsonify(usr.get_api_data(include_secrets=True)),
158 158 }
159 159 try:
160 160 expected = ret
161 161 assert_ok(id_, expected, given=response.body)
162 162 finally:
163 163 fixture.destroy_user(usr.user_id)
164 164
165 165 def test_api_create_user_with_personal_repo_group(self):
166 166 username = 'test_new_api_user_personal_group'
167 167 email = username + "@foo.com"
168 168
169 169 id_, params = build_data(
170 170 self.apikey, 'create_user',
171 171 username=username,
172 172 email=email, extern_name='rhodecode',
173 173 create_personal_repo_group=True)
174 174 response = api_call(self.app, params)
175 175
176 176 usr = UserModel().get_by_username(username)
177 177 ret = {
178 178 'msg': 'created new user `%s`' % (username,),
179 179 'user': jsonify(usr.get_api_data(include_secrets=True)),
180 180 }
181 181
182 182 personal_group = RepoGroup.get_by_group_name(username)
183 183 assert personal_group
184 184 assert personal_group.personal == True
185 185 assert personal_group.user.username == username
186 186
187 187 try:
188 188 expected = ret
189 189 assert_ok(id_, expected, given=response.body)
190 190 finally:
191 191 fixture.destroy_repo_group(username)
192 192 fixture.destroy_user(usr.user_id)
193 193
194 194 @mock.patch.object(UserModel, 'create_or_update', crash)
195 195 def test_api_create_user_when_exception_happened(self):
196 196
197 197 username = 'test_new_api_user'
198 198 email = username + "@foo.com"
199 199
200 200 id_, params = build_data(
201 201 self.apikey, 'create_user',
202 202 username=username,
203 203 email=email,
204 204 password='trololo')
205 205 response = api_call(self.app, params)
206 206 expected = 'failed to create user `%s`' % (username,)
207 207 assert_error(id_, expected, given=response.body)
@@ -1,127 +1,127 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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,115 +1,115 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 from rhodecode.tests import HG_REPO
23 23 from rhodecode.api.tests.utils import (
24 24 build_data, api_call, assert_error, assert_ok)
25 25
26 26
27 27 @pytest.mark.usefixtures("testuser_api", "app")
28 28 class TestApiSearch(object):
29 29
30 30 @pytest.mark.parametrize("sort_dir", [
31 31 "asc",
32 32 "desc",
33 33 ])
34 34 @pytest.mark.parametrize("sort", [
35 35 "xxx",
36 36 "author_email",
37 37 "date",
38 38 "message",
39 39 ])
40 40 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
41 41 ('todo', 23, [
42 42 'vcs/backends/hg/inmemory.py',
43 43 'vcs/tests/test_git.py']),
44 44 ('extension:rst installation', 6, [
45 45 'docs/index.rst',
46 46 'docs/installation.rst']),
47 47 ('def repo', 87, [
48 48 'vcs/tests/test_git.py',
49 49 'vcs/tests/test_changesets.py']),
50 50 ('repository:%s def test' % HG_REPO, 18, [
51 51 'vcs/tests/test_git.py',
52 52 'vcs/tests/test_changesets.py']),
53 53 ('"def main"', 9, [
54 54 'vcs/__init__.py',
55 55 'vcs/tests/__init__.py',
56 56 'vcs/utils/progressbar.py']),
57 57 ('owner:test_admin', 358, [
58 58 'vcs/tests/base.py',
59 59 'MANIFEST.in',
60 60 'vcs/utils/termcolors.py',
61 61 'docs/theme/ADC/static/documentation.png']),
62 62 ('owner:test_admin def main', 72, [
63 63 'vcs/__init__.py',
64 64 'vcs/tests/test_utils_filesize.py',
65 65 'vcs/tests/test_cli.py']),
66 66 ('owner:michał test', 0, []),
67 67 ])
68 68 def test_search_content_results(self, sort_dir, sort, query, expected_hits, expected_paths):
69 69 id_, params = build_data(
70 70 self.apikey_regular, 'search',
71 71 search_query=query,
72 72 search_sort='{}:{}'.format(sort_dir, sort),
73 73 search_type='content')
74 74
75 75 response = api_call(self.app, params)
76 76 json_response = response.json
77 77
78 78 assert json_response['result']['item_count'] == expected_hits
79 79 paths = [x['f_path'] for x in json_response['result']['results']]
80 80
81 81 for expected_path in expected_paths:
82 82 assert expected_path in paths
83 83
84 84 @pytest.mark.parametrize("sort_dir", [
85 85 "asc",
86 86 "desc",
87 87 ])
88 88 @pytest.mark.parametrize("sort", [
89 89 "xxx",
90 90 "date",
91 91 "file",
92 92 "size",
93 93 ])
94 94 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
95 95 ('readme.rst', 3, []),
96 96 ('test*', 75, []),
97 97 ('*model*', 1, []),
98 98 ('extension:rst', 48, []),
99 99 ('extension:rst api', 24, []),
100 100 ])
101 101 def test_search_file_paths(self, sort_dir, sort, query, expected_hits, expected_paths):
102 102 id_, params = build_data(
103 103 self.apikey_regular, 'search',
104 104 search_query=query,
105 105 search_sort='{}:{}'.format(sort_dir, sort),
106 106 search_type='path')
107 107
108 108 response = api_call(self.app, params)
109 109 json_response = response.json
110 110
111 111 assert json_response['result']['item_count'] == expected_hits
112 112 paths = [x['f_path'] for x in json_response['result']['results']]
113 113
114 114 for expected_path in expected_paths:
115 115 assert expected_path in paths
@@ -1,101 +1,101 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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,61 +1,61 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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', 'get_repo_comments']
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 'extra_recipients': '<Optional:[]>',
54 54 'message': '<RequiredType>',
55 55 'repoid': '<RequiredType>',
56 56 'request': '<RequiredType>',
57 57 'resolves_comment_id': '<Optional:None>',
58 58 'status': '<Optional:None>',
59 59 'userid': '<Optional:<OptionalAttr:apiuser>>',
60 60 'send_email': '<Optional:True>'}]
61 61 assert_ok(id_, expected, given=response.body)
@@ -1,143 +1,143 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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, merge_state=True)
42 42
43 43 response = api_call(self.app, params)
44 44
45 45 assert response.status == '200 OK'
46 46
47 47 url_obj = urlobject.URLObject(
48 48 h.route_url(
49 49 'pullrequest_show',
50 50 repo_name=pull_request.target_repo.repo_name,
51 51 pull_request_id=pull_request.pull_request_id))
52 52
53 53 pr_url = safe_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 'state': pull_request.pull_request_state,
69 69 'created_on': pull_request.created_on,
70 70 'updated_on': pull_request.updated_on,
71 71 'commit_ids': pull_request.revisions,
72 72 'review_status': pull_request.calculated_review_status(),
73 73 'mergeable': {
74 74 'status': True,
75 75 'message': 'This pull request can be automatically merged.',
76 76 },
77 77 'source': {
78 78 'clone_url': source_url,
79 79 'repository': pull_request.source_repo.repo_name,
80 80 'reference': {
81 81 'name': pull_request.source_ref_parts.name,
82 82 'type': pull_request.source_ref_parts.type,
83 83 'commit_id': pull_request.source_ref_parts.commit_id,
84 84 },
85 85 },
86 86 'target': {
87 87 'clone_url': target_url,
88 88 'repository': pull_request.target_repo.repo_name,
89 89 'reference': {
90 90 'name': pull_request.target_ref_parts.name,
91 91 'type': pull_request.target_ref_parts.type,
92 92 'commit_id': pull_request.target_ref_parts.commit_id,
93 93 },
94 94 },
95 95 'merge': {
96 96 'clone_url': shadow_url,
97 97 'reference': {
98 98 'name': pull_request.shadow_merge_ref.name,
99 99 'type': pull_request.shadow_merge_ref.type,
100 100 'commit_id': pull_request.shadow_merge_ref.commit_id,
101 101 },
102 102 },
103 103 'author': pull_request.author.get_api_data(include_secrets=False,
104 104 details='basic'),
105 105 'reviewers': [
106 106 {
107 107 'user': reviewer.get_api_data(include_secrets=False,
108 108 details='basic'),
109 109 'reasons': reasons,
110 110 'review_status': st[0][1].status if st else 'not_reviewed',
111 111 }
112 112 for obj, reviewer, reasons, mandatory, st in
113 113 pull_request.reviewers_statuses()
114 114 ]
115 115 }
116 116 assert_ok(id_, expected, response.body)
117 117
118 118 def test_api_get_pull_request_repo_error(self, pr_util):
119 119 pull_request = pr_util.create_pull_request()
120 120 id_, params = build_data(
121 121 self.apikey, 'get_pull_request',
122 122 repoid=666, pullrequestid=pull_request.pull_request_id)
123 123 response = api_call(self.app, params)
124 124
125 125 expected = 'repository `666` does not exist'
126 126 assert_error(id_, expected, given=response.body)
127 127
128 128 def test_api_get_pull_request_pull_request_error(self):
129 129 id_, params = build_data(
130 130 self.apikey, 'get_pull_request', pullrequestid=666)
131 131 response = api_call(self.app, params)
132 132
133 133 expected = 'pull request `666` does not exist'
134 134 assert_error(id_, expected, given=response.body)
135 135
136 136 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
137 137 id_, params = build_data(
138 138 self.apikey, 'get_pull_request',
139 139 pullrequestid=666)
140 140 response = api_call(self.app, params)
141 141
142 142 expected = 'pull request `666` does not exist'
143 143 assert_error(id_, expected, given=response.body)
@@ -1,86 +1,86 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 'comment_resolved_by': None,
63 63 'pull_request_version': None,
64 64 'comment_commit_id': None,
65 65 'comment_pull_request_id': pull_request.pull_request_id
66 66 }
67 67 ]
68 68 assert_ok(id_, expected, response.body)
69 69
70 70 def test_api_get_pull_request_comments_repo_error(self, pr_util):
71 71 pull_request = pr_util.create_pull_request()
72 72 id_, params = build_data(
73 73 self.apikey, 'get_pull_request_comments',
74 74 repoid=666, pullrequestid=pull_request.pull_request_id)
75 75 response = api_call(self.app, params)
76 76
77 77 expected = 'repository `666` does not exist'
78 78 assert_error(id_, expected, given=response.body)
79 79
80 80 def test_api_get_pull_request_comments_pull_request_error(self):
81 81 id_, params = build_data(
82 82 self.apikey, 'get_pull_request_comments', pullrequestid=666)
83 83 response = api_call(self.app, params)
84 84
85 85 expected = 'pull request `666` does not exist'
86 86 assert_error(id_, expected, given=response.body)
@@ -1,80 +1,80 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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,143 +1,143 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 repo_id = repo.repo_id
48 48 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
49 49 group = user_util.create_user_group(members=[usr])
50 50 user_util.grant_user_group_permission_to_repo(
51 51 repo=repo, user_group=group, permission_name='repository.read')
52 52 Session().commit()
53 53 kwargs = {
54 54 'repoid': repo.repo_name,
55 55 }
56 56 if cache_param is not None:
57 57 kwargs['cache'] = cache_param
58 58
59 59 apikey = getattr(self, apikey_attr)
60 60 id_, params = build_data(apikey, 'get_repo', **kwargs)
61 61 response = api_call(self.app, params)
62 62
63 63 ret = repo.get_api_data()
64 64
65 65 permissions = expected_permissions(repo)
66 66
67 67 followers = []
68 68
69 69 repo = RepoModel().get(repo_id)
70 70 for user in repo.followers:
71 71 followers.append(user.user.get_api_data(
72 72 include_secrets=expect_secrets))
73 73
74 74 ret['permissions'] = permissions
75 75 ret['followers'] = followers
76 76
77 77 expected = ret
78 78
79 79 assert_ok(id_, expected, given=response.body)
80 80
81 81 @pytest.mark.parametrize("grant_perm", [
82 82 'repository.admin',
83 83 'repository.write',
84 84 'repository.read',
85 85 ])
86 86 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
87 87 # TODO: Depending on which tests are running before this one, we
88 88 # start with a different number of permissions in the database.
89 89 repo = RepoModel().get_by_repo_name(backend.repo_name)
90 90 repo_id = repo.repo_id
91 91 permission_count = len(repo.repo_to_perm)
92 92
93 93 RepoModel().grant_user_permission(repo=backend.repo_name,
94 94 user=self.TEST_USER_LOGIN,
95 95 perm=grant_perm)
96 96 Session().commit()
97 97 id_, params = build_data(
98 98 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
99 99 response = api_call(self.app, params)
100 100
101 101 repo = RepoModel().get_by_repo_name(backend.repo_name)
102 102 ret = repo.get_api_data()
103 103
104 104 assert permission_count + 1, len(repo.repo_to_perm)
105 105
106 106 permissions = expected_permissions(repo)
107 107
108 108 followers = []
109 109
110 110 repo = RepoModel().get(repo_id)
111 111 for user in repo.followers:
112 112 followers.append(user.user.get_api_data())
113 113
114 114 ret['permissions'] = permissions
115 115 ret['followers'] = followers
116 116
117 117 expected = ret
118 118 try:
119 119 assert_ok(id_, expected, given=response.body)
120 120 finally:
121 121 RepoModel().revoke_user_permission(
122 122 backend.repo_name, self.TEST_USER_LOGIN)
123 123
124 124 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
125 125 RepoModel().grant_user_permission(repo=backend.repo_name,
126 126 user=self.TEST_USER_LOGIN,
127 127 perm='repository.none')
128 128
129 129 id_, params = build_data(
130 130 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
131 131 response = api_call(self.app, params)
132 132
133 133 expected = 'repository `%s` does not exist' % (backend.repo_name)
134 134 assert_error(id_, expected, given=response.body)
135 135
136 136 def test_api_get_repo_not_existing(self):
137 137 id_, params = build_data(
138 138 self.apikey, 'get_repo', repoid='no-such-repo')
139 139 response = api_call(self.app, params)
140 140
141 141 ret = 'repository `%s` does not exist' % 'no-such-repo'
142 142 expected = ret
143 143 assert_error(id_, expected, given=response.body)
@@ -1,141 +1,141 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 got <type 'int'> instead"
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 got <type 'int'> instead"
141 141 assert_error(id_, expected, given=response.body)
@@ -1,110 +1,110 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 User, ChangesetComment
25 25 from rhodecode.model.meta import Session
26 26 from rhodecode.model.comment import CommentsModel
27 27 from rhodecode.api.tests.utils import (
28 28 build_data, api_call, assert_error, assert_call_ok)
29 29
30 30
31 31 @pytest.fixture()
32 32 def make_repo_comments_factory(request):
33 33
34 34 class Make(object):
35 35
36 36 def make_comments(self, repo):
37 37 user = User.get_first_super_admin()
38 38 commit = repo.scm_instance()[0]
39 39
40 40 commit_id = commit.raw_id
41 41 file_0 = commit.affected_files[0]
42 42 comments = []
43 43
44 44 # general
45 45 CommentsModel().create(
46 46 text='General Comment', repo=repo, user=user, commit_id=commit_id,
47 47 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
48 48
49 49 # inline
50 50 CommentsModel().create(
51 51 text='Inline Comment', repo=repo, user=user, commit_id=commit_id,
52 52 f_path=file_0, line_no='n1',
53 53 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
54 54
55 55 # todo
56 56 CommentsModel().create(
57 57 text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id,
58 58 f_path=file_0, line_no='n1',
59 59 comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False)
60 60
61 61 @request.addfinalizer
62 62 def cleanup():
63 63 for comment in comments:
64 64 Session().delete(comment)
65 65 return Make()
66 66
67 67
68 68 @pytest.mark.usefixtures("testuser_api", "app")
69 69 class TestGetRepo(object):
70 70
71 71 @pytest.mark.parametrize('filters, expected_count', [
72 72 ({}, 3),
73 73 ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2),
74 74 ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1),
75 75 ({'commit_id': 'FILLED DYNAMIC'}, 3),
76 76 ])
77 77 def test_api_get_repo_comments(self, backend, user_util,
78 78 make_repo_comments_factory, filters, expected_count):
79 79 commits = [{'message': 'A'}, {'message': 'B'}]
80 80 repo = backend.create_repo(commits=commits)
81 81 make_repo_comments_factory.make_comments(repo)
82 82
83 83 api_call_params = {'repoid': repo.repo_name,}
84 84 api_call_params.update(filters)
85 85
86 86 if 'commit_id' in api_call_params:
87 87 commit = repo.scm_instance()[0]
88 88 commit_id = commit.raw_id
89 89 api_call_params['commit_id'] = commit_id
90 90
91 91 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
92 92 response = api_call(self.app, params)
93 93 result = assert_call_ok(id_, given=response.body)
94 94
95 95 assert len(result) == expected_count
96 96
97 97 def test_api_get_repo_comments_wrong_comment_type(
98 98 self, make_repo_comments_factory, backend_hg):
99 99 commits = [{'message': 'A'}, {'message': 'B'}]
100 100 repo = backend_hg.create_repo(commits=commits)
101 101 make_repo_comments_factory.make_comments(repo)
102 102
103 103 api_call_params = {'repoid': repo.repo_name}
104 104 api_call_params.update({'comment_type': 'bogus'})
105 105
106 106 expected = 'comment_type must be one of `{}` got {}'.format(
107 107 ChangesetComment.COMMENT_TYPES, 'bogus')
108 108 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
109 109 response = api_call(self.app, params)
110 110 assert_error(id_, expected, given=response.body)
@@ -1,55 +1,55 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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,173 +1,173 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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' % (repo_group.name,)
136 136 assert_error(id_, expected, given=response.body)
137 137
138 138 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
139 139 self, user_util):
140 140 user_group = user_util.create_user_group()
141 141 repo_group = user_util.create_repo_group()
142 142 perm = 'haha.no.permission'
143 143 id_, params = build_data(
144 144 self.apikey,
145 145 'grant_user_group_permission_to_repo_group',
146 146 repogroupid=repo_group.name,
147 147 usergroupid=user_group.users_group_name,
148 148 perm=perm)
149 149 response = api_call(self.app, params)
150 150
151 151 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,)
152 152 assert_error(id_, expected, given=response.body)
153 153
154 154 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
155 155 def test_api_grant_user_group_permission_exception_when_adding_2(
156 156 self, user_util):
157 157 user_group = user_util.create_user_group()
158 158 repo_group = user_util.create_repo_group()
159 159 perm = 'group.read'
160 160 id_, params = build_data(
161 161 self.apikey,
162 162 'grant_user_group_permission_to_repo_group',
163 163 repogroupid=repo_group.name,
164 164 usergroupid=user_group.users_group_name,
165 165 perm=perm)
166 166 response = api_call(self.app, params)
167 167
168 168 expected = (
169 169 'failed to edit permission for user group: `%s`'
170 170 ' in repo group: `%s`' % (
171 171 user_group.users_group_name, repo_group.name)
172 172 )
173 173 assert_error(id_, expected, given=response.body)
@@ -1,97 +1,97 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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. Permission should start with prefix: `group.`' % (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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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. Permission should start with prefix: `usergroup.`' % 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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,259 +1,259 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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
33 33 @pytest.mark.backends("git", "hg")
34 34 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
35 35 pull_request = pr_util.create_pull_request(mergeable=True)
36 36 pull_request_id = pull_request.pull_request_id
37 37 pull_request_repo = pull_request.target_repo.repo_name
38 38
39 39 id_, params = build_data(
40 40 self.apikey, 'merge_pull_request',
41 41 repoid=pull_request_repo,
42 42 pullrequestid=pull_request_id)
43 43
44 44 response = api_call(self.app, params)
45 45
46 46 # The above api call detaches the pull request DB object from the
47 47 # session because of an unconditional transaction rollback in our
48 48 # middleware. Therefore we need to add it back here if we want to use it.
49 49 Session().add(pull_request)
50 50
51 51 expected = 'merge not possible for following reasons: ' \
52 52 'Pull request reviewer approval is pending.'
53 53 assert_error(id_, expected, given=response.body)
54 54
55 55 @pytest.mark.backends("git", "hg")
56 56 def test_api_merge_pull_request_merge_failed_disallowed_state(
57 57 self, pr_util, no_notifications):
58 58 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
59 59 pull_request_id = pull_request.pull_request_id
60 60 pull_request_repo = pull_request.target_repo.repo_name
61 61
62 62 pr = PullRequest.get(pull_request_id)
63 63 pr.pull_request_state = pull_request.STATE_UPDATING
64 64 Session().add(pr)
65 65 Session().commit()
66 66
67 67 id_, params = build_data(
68 68 self.apikey, 'merge_pull_request',
69 69 repoid=pull_request_repo,
70 70 pullrequestid=pull_request_id)
71 71
72 72 response = api_call(self.app, params)
73 73 expected = 'Operation forbidden because pull request is in state {}, '\
74 74 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING,
75 75 PullRequest.STATE_CREATED)
76 76 assert_error(id_, expected, given=response.body)
77 77
78 78 @pytest.mark.backends("git", "hg")
79 79 def test_api_merge_pull_request(self, pr_util, no_notifications):
80 80 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
81 81 author = pull_request.user_id
82 82 repo = pull_request.target_repo.repo_id
83 83 pull_request_id = pull_request.pull_request_id
84 84 pull_request_repo = pull_request.target_repo.repo_name
85 85
86 86 id_, params = build_data(
87 87 self.apikey, 'comment_pull_request',
88 88 repoid=pull_request_repo,
89 89 pullrequestid=pull_request_id,
90 90 status='approved')
91 91
92 92 response = api_call(self.app, params)
93 93 expected = {
94 94 'comment_id': response.json.get('result', {}).get('comment_id'),
95 95 'pull_request_id': pull_request_id,
96 96 'status': {'given': 'approved', 'was_changed': True}
97 97 }
98 98 assert_ok(id_, expected, given=response.body)
99 99
100 100 id_, params = build_data(
101 101 self.apikey, 'merge_pull_request',
102 102 repoid=pull_request_repo,
103 103 pullrequestid=pull_request_id)
104 104
105 105 response = api_call(self.app, params)
106 106
107 107 pull_request = PullRequest.get(pull_request_id)
108 108
109 109 expected = {
110 110 'executed': True,
111 111 'failure_reason': 0,
112 112 'merge_status_message': 'This pull request can be automatically merged.',
113 113 'possible': True,
114 114 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
115 115 'merge_ref': pull_request.shadow_merge_ref._asdict()
116 116 }
117 117
118 118 assert_ok(id_, expected, response.body)
119 119
120 120 journal = UserLog.query()\
121 121 .filter(UserLog.user_id == author)\
122 122 .filter(UserLog.repository_id == repo) \
123 123 .order_by(UserLog.user_log_id.asc()) \
124 124 .all()
125 125 assert journal[-2].action == 'repo.pull_request.merge'
126 126 assert journal[-1].action == 'repo.pull_request.close'
127 127
128 128 id_, params = build_data(
129 129 self.apikey, 'merge_pull_request',
130 130 repoid=pull_request_repo, pullrequestid=pull_request_id)
131 131 response = api_call(self.app, params)
132 132
133 133 expected = 'merge not possible for following reasons: This pull request is closed.'
134 134 assert_error(id_, expected, given=response.body)
135 135
136 136 @pytest.mark.backends("git", "hg")
137 137 def test_api_merge_pull_request_as_another_user_no_perms_to_merge(
138 138 self, pr_util, no_notifications, user_util):
139 139 merge_user = user_util.create_user()
140 140 merge_user_id = merge_user.user_id
141 141 merge_user_username = merge_user.username
142 142
143 143 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
144 144
145 145 pull_request_id = pull_request.pull_request_id
146 146 pull_request_repo = pull_request.target_repo.repo_name
147 147
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 status='approved')
153 153
154 154 response = api_call(self.app, params)
155 155 expected = {
156 156 'comment_id': response.json.get('result', {}).get('comment_id'),
157 157 'pull_request_id': pull_request_id,
158 158 'status': {'given': 'approved', 'was_changed': True}
159 159 }
160 160 assert_ok(id_, expected, given=response.body)
161 161 id_, params = build_data(
162 162 self.apikey, 'merge_pull_request',
163 163 repoid=pull_request_repo,
164 164 pullrequestid=pull_request_id,
165 165 userid=merge_user_id
166 166 )
167 167
168 168 response = api_call(self.app, params)
169 169 expected = 'merge not possible for following reasons: User `{}` ' \
170 170 'not allowed to perform merge.'.format(merge_user_username)
171 171 assert_error(id_, expected, response.body)
172 172
173 173 @pytest.mark.backends("git", "hg")
174 174 def test_api_merge_pull_request_as_another_user(self, pr_util, no_notifications, user_util):
175 175 merge_user = user_util.create_user()
176 176 merge_user_id = merge_user.user_id
177 177 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
178 178 user_util.grant_user_permission_to_repo(
179 179 pull_request.target_repo, merge_user, 'repository.write')
180 180 author = pull_request.user_id
181 181 repo = pull_request.target_repo.repo_id
182 182 pull_request_id = pull_request.pull_request_id
183 183 pull_request_repo = pull_request.target_repo.repo_name
184 184
185 185 id_, params = build_data(
186 186 self.apikey, 'comment_pull_request',
187 187 repoid=pull_request_repo,
188 188 pullrequestid=pull_request_id,
189 189 status='approved')
190 190
191 191 response = api_call(self.app, params)
192 192 expected = {
193 193 'comment_id': response.json.get('result', {}).get('comment_id'),
194 194 'pull_request_id': pull_request_id,
195 195 'status': {'given': 'approved', 'was_changed': True}
196 196 }
197 197 assert_ok(id_, expected, given=response.body)
198 198
199 199 id_, params = build_data(
200 200 self.apikey, 'merge_pull_request',
201 201 repoid=pull_request_repo,
202 202 pullrequestid=pull_request_id,
203 203 userid=merge_user_id
204 204 )
205 205
206 206 response = api_call(self.app, params)
207 207
208 208 pull_request = PullRequest.get(pull_request_id)
209 209
210 210 expected = {
211 211 'executed': True,
212 212 'failure_reason': 0,
213 213 'merge_status_message': 'This pull request can be automatically merged.',
214 214 'possible': True,
215 215 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
216 216 'merge_ref': pull_request.shadow_merge_ref._asdict()
217 217 }
218 218
219 219 assert_ok(id_, expected, response.body)
220 220
221 221 journal = UserLog.query() \
222 222 .filter(UserLog.user_id == merge_user_id) \
223 223 .filter(UserLog.repository_id == repo) \
224 224 .order_by(UserLog.user_log_id.asc()) \
225 225 .all()
226 226 assert journal[-2].action == 'repo.pull_request.merge'
227 227 assert journal[-1].action == 'repo.pull_request.close'
228 228
229 229 id_, params = build_data(
230 230 self.apikey, 'merge_pull_request',
231 231 repoid=pull_request_repo, pullrequestid=pull_request_id, userid=merge_user_id)
232 232 response = api_call(self.app, params)
233 233
234 234 expected = 'merge not possible for following reasons: This pull request is closed.'
235 235 assert_error(id_, expected, given=response.body)
236 236
237 237 @pytest.mark.backends("git", "hg")
238 238 def test_api_merge_pull_request_repo_error(self, pr_util):
239 239 pull_request = pr_util.create_pull_request()
240 240 id_, params = build_data(
241 241 self.apikey, 'merge_pull_request',
242 242 repoid=666, pullrequestid=pull_request.pull_request_id)
243 243 response = api_call(self.app, params)
244 244
245 245 expected = 'repository `666` does not exist'
246 246 assert_error(id_, expected, given=response.body)
247 247
248 248 @pytest.mark.backends("git", "hg")
249 249 def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util):
250 250 pull_request = pr_util.create_pull_request(mergeable=True)
251 251 id_, params = build_data(
252 252 self.apikey_regular, 'merge_pull_request',
253 253 repoid=pull_request.target_repo.repo_name,
254 254 pullrequestid=pull_request.pull_request_id,
255 255 userid=TEST_USER_ADMIN_LOGIN)
256 256 response = api_call(self.app, params)
257 257
258 258 expected = 'userid is not the same as your user'
259 259 assert_error(id_, expected, given=response.body)
@@ -1,55 +1,55 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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,121 +1,121 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 ('description', 'CTO 4 Life')
47 47 ])
48 48 def test_api_update_user(self, name, expected, user_util):
49 49 usr = user_util.create_user()
50 50
51 51 kw = {name: expected, 'userid': usr.user_id}
52 52 id_, params = build_data(self.apikey, 'update_user', **kw)
53 53 response = api_call(self.app, params)
54 54
55 55 ret = {
56 56 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
57 57 'user': jsonify(
58 58 UserModel()
59 59 .get_by_username(usr.username)
60 60 .get_api_data(include_secrets=True)
61 61 )
62 62 }
63 63
64 64 expected = ret
65 65 assert_ok(id_, expected, given=response.body)
66 66
67 67 def test_api_update_user_no_changed_params(self):
68 68 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
69 69 ret = jsonify(usr.get_api_data(include_secrets=True))
70 70 id_, params = build_data(
71 71 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
72 72
73 73 response = api_call(self.app, params)
74 74 ret = {
75 75 'msg': 'updated user ID:%s %s' % (
76 76 usr.user_id, TEST_USER_ADMIN_LOGIN),
77 77 'user': ret
78 78 }
79 79 expected = ret
80 80 expected['user']['last_activity'] = response.json['result']['user'][
81 81 'last_activity']
82 82 assert_ok(id_, expected, given=response.body)
83 83
84 84 def test_api_update_user_by_user_id(self):
85 85 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
86 86 ret = jsonify(usr.get_api_data(include_secrets=True))
87 87 id_, params = build_data(
88 88 self.apikey, 'update_user', userid=usr.user_id)
89 89
90 90 response = api_call(self.app, params)
91 91 ret = {
92 92 'msg': 'updated user ID:%s %s' % (
93 93 usr.user_id, TEST_USER_ADMIN_LOGIN),
94 94 'user': ret
95 95 }
96 96 expected = ret
97 97 expected['user']['last_activity'] = response.json['result']['user'][
98 98 'last_activity']
99 99 assert_ok(id_, expected, given=response.body)
100 100
101 101 def test_api_update_user_default_user(self):
102 102 usr = User.get_default_user()
103 103 id_, params = build_data(
104 104 self.apikey, 'update_user', userid=usr.user_id)
105 105
106 106 response = api_call(self.app, params)
107 107 expected = 'editing default user is forbidden'
108 108 assert_error(id_, expected, given=response.body)
109 109
110 110 @mock.patch.object(UserModel, 'update_user', crash)
111 111 def test_api_update_user_when_exception_happens(self):
112 112 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
113 113 ret = jsonify(usr.get_api_data(include_secrets=True))
114 114 id_, params = build_data(
115 115 self.apikey, 'update_user', userid=usr.user_id)
116 116
117 117 response = api_call(self.app, params)
118 118 ret = 'failed to update user `%s`' % (usr.user_id,)
119 119
120 120 expected = ret
121 121 assert_error(id_, expected, given=response.body)
@@ -1,125 +1,125 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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,122 +1,122 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 import pytest
24 24
25 25 from rhodecode.api.utils import get_origin
26 26 from rhodecode.lib.ext_json import json
27 27
28 28
29 29 def jsonify(obj):
30 30 return json.loads(json.dumps(obj))
31 31
32 32
33 33 API_URL = '/_admin/api'
34 34
35 35
36 36 def assert_call_ok(id_, given):
37 37 expected = jsonify({
38 38 'id': id_,
39 39 'error': None,
40 40 'result': None
41 41 })
42 42 given = json.loads(given)
43 43
44 44 assert expected['id'] == given['id']
45 45 assert expected['error'] == given['error']
46 46 return given['result']
47 47
48 48
49 49 def assert_ok(id_, expected, given):
50 50 given = json.loads(given)
51 51 if given.get('error'):
52 52 pytest.fail("Unexpected ERROR in success response: {}".format(given['error']))
53 53
54 54 expected = jsonify({
55 55 'id': id_,
56 56 'error': None,
57 57 'result': expected
58 58 })
59 59
60 60 assert expected == given
61 61
62 62
63 63 def assert_error(id_, expected, given):
64 64 expected = jsonify({
65 65 'id': id_,
66 66 'error': expected,
67 67 'result': None
68 68 })
69 69 given = json.loads(given)
70 70 assert expected == given
71 71
72 72
73 73 def build_data(apikey, method, **kw):
74 74 """
75 75 Builds API data with given random ID
76 76 """
77 77 random_id = random.randrange(1, 9999)
78 78 return random_id, json.dumps({
79 79 "id": random_id,
80 80 "api_key": apikey,
81 81 "method": method,
82 82 "args": kw
83 83 })
84 84
85 85
86 86 def api_call(app, params, status=None):
87 87 response = app.post(
88 88 API_URL, content_type='application/json', params=params, status=status)
89 89 return response
90 90
91 91
92 92 def crash(*args, **kwargs):
93 93 raise Exception('Total Crash !')
94 94
95 95
96 96 def expected_permissions(object_with_permissions):
97 97 """
98 98 Returns the expected permissions structure for the given object.
99 99
100 100 The object is expected to be a `Repository`, `RepositoryGroup`,
101 101 or `UserGroup`. They all implement the same permission handling
102 102 API.
103 103 """
104 104 permissions = []
105 105 for _user in object_with_permissions.permissions():
106 106 user_data = {
107 107 'name': _user.username,
108 108 'permission': _user.permission,
109 109 'origin': get_origin(_user),
110 110 'type': "user",
111 111 }
112 112 permissions.append(user_data)
113 113
114 114 for _user_group in object_with_permissions.permission_user_groups():
115 115 user_group_data = {
116 116 'name': _user_group.users_group_name,
117 117 'permission': _user_group.permission,
118 118 'origin': get_origin(_user_group),
119 119 'type': "user_group",
120 120 }
121 121 permissions.append(user_group_data)
122 122 return permissions
@@ -1,453 +1,453 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2014-2019 RhodeCode GmbH
3 # Copyright (C) 2014-2020 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 msg = 'permission `{}` does not exist.'.format(permid)
312 312 if prefix:
313 313 msg += ' Permission should start with prefix: `{}`'.format(prefix)
314 314 raise JSONRPCError(msg)
315 315
316 316 if prefix:
317 317 if not perm.permission_name.startswith(prefix):
318 318 raise JSONRPCError('permission `%s` is invalid, '
319 319 'should start with %s' % (permid, prefix))
320 320 return perm
321 321
322 322
323 323 def get_gist_or_error(gistid):
324 324 """
325 325 Get gist by id or gist_access_id or return JsonRPCError if not found
326 326
327 327 :param gistid:
328 328 """
329 329 from rhodecode.model.gist import GistModel
330 330
331 331 gist = GistModel.cls.get_by_access_id(gistid)
332 332 if gist is None:
333 333 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
334 334 return gist
335 335
336 336
337 337 def get_pull_request_or_error(pullrequestid):
338 338 """
339 339 Get pull request by id or return JsonRPCError if not found
340 340
341 341 :param pullrequestid:
342 342 """
343 343 from rhodecode.model.pull_request import PullRequestModel
344 344
345 345 try:
346 346 pull_request = PullRequestModel().get(int(pullrequestid))
347 347 except ValueError:
348 348 raise JSONRPCError('pullrequestid must be an integer')
349 349 if not pull_request:
350 350 raise JSONRPCError('pull request `%s` does not exist' % (
351 351 pullrequestid,))
352 352 return pull_request
353 353
354 354
355 355 def build_commit_data(commit, detail_level):
356 356 parsed_diff = []
357 357 if detail_level == 'extended':
358 358 for f_path in commit.added_paths:
359 359 parsed_diff.append(_get_commit_dict(filename=f_path, op='A'))
360 360 for f_path in commit.changed_paths:
361 361 parsed_diff.append(_get_commit_dict(filename=f_path, op='M'))
362 362 for f_path in commit.removed_paths:
363 363 parsed_diff.append(_get_commit_dict(filename=f_path, op='D'))
364 364
365 365 elif detail_level == 'full':
366 366 from rhodecode.lib.diffs import DiffProcessor
367 367 diff_processor = DiffProcessor(commit.diff())
368 368 for dp in diff_processor.prepare():
369 369 del dp['stats']['ops']
370 370 _stats = dp['stats']
371 371 parsed_diff.append(_get_commit_dict(
372 372 filename=dp['filename'], op=dp['operation'],
373 373 new_revision=dp['new_revision'],
374 374 old_revision=dp['old_revision'],
375 375 raw_diff=dp['raw_diff'], stats=_stats))
376 376
377 377 return parsed_diff
378 378
379 379
380 380 def get_commit_or_error(ref, repo):
381 381 try:
382 382 ref_type, _, ref_hash = ref.split(':')
383 383 except ValueError:
384 384 raise JSONRPCError(
385 385 'Ref `{ref}` given in a wrong format. Please check the API'
386 386 ' documentation for more details'.format(ref=ref))
387 387 try:
388 388 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
389 389 # once get_commit supports ref_types
390 390 return get_commit_from_ref_name(repo, ref_hash)
391 391 except RepositoryError:
392 392 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
393 393
394 394
395 395 def _get_ref_hash(repo, type_, name):
396 396 vcs_repo = repo.scm_instance()
397 397 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
398 398 return vcs_repo.branches[name]
399 399 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
400 400 return vcs_repo.bookmarks[name]
401 401 else:
402 402 raise ValueError()
403 403
404 404
405 405 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
406 406 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
407 407
408 408 def _parse_ref(type_, name, hash_=None):
409 409 return type_, name, hash_
410 410
411 411 try:
412 412 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
413 413 except TypeError:
414 414 raise JSONRPCError(
415 415 'Ref `{ref}` given in a wrong format. Please check the API'
416 416 ' documentation for more details'.format(ref=ref))
417 417
418 418 if ref_type not in allowed_ref_types:
419 419 raise JSONRPCError(
420 420 'Ref `{ref}` type is not allowed. '
421 421 'Only:{allowed_refs} are possible.'.format(
422 422 ref=ref, allowed_refs=allowed_ref_types))
423 423
424 424 try:
425 425 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
426 426 except (KeyError, ValueError):
427 427 raise JSONRPCError(
428 428 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
429 429 type=ref_type, name=ref_name))
430 430
431 431 return ':'.join([ref_type, ref_name, ref_hash])
432 432
433 433
434 434 def _get_commit_dict(
435 435 filename, op, new_revision=None, old_revision=None,
436 436 raw_diff=None, stats=None):
437 437 if stats is None:
438 438 stats = {
439 439 "added": None,
440 440 "binary": None,
441 441 "deleted": None
442 442 }
443 443 return {
444 444 "filename": safe_unicode(filename),
445 445 "op": op,
446 446
447 447 # extra details
448 448 "new_revision": new_revision,
449 449 "old_revision": old_revision,
450 450
451 451 "raw_diff": raw_diff,
452 452 "stats": stats
453 453 }
@@ -1,19 +1,19 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2015-2019 RhodeCode GmbH
3 # Copyright (C) 2015-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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,1018 +1,1018 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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, validate_set_owner_permissions)
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, PullRequest
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 merge_state=Optional(False)):
48 48 """
49 49 Get a pull request based on the given ID.
50 50
51 51 :param apiuser: This is filled automatically from the |authtoken|.
52 52 :type apiuser: AuthUser
53 53 :param repoid: Optional, repository name or repository ID from where
54 54 the pull request was opened.
55 55 :type repoid: str or int
56 56 :param pullrequestid: ID of the requested pull request.
57 57 :type pullrequestid: int
58 58 :param merge_state: Optional calculate merge state for each repository.
59 59 This could result in longer time to fetch the data
60 60 :type merge_state: bool
61 61
62 62 Example output:
63 63
64 64 .. code-block:: bash
65 65
66 66 "id": <id_given_in_input>,
67 67 "result":
68 68 {
69 69 "pull_request_id": "<pull_request_id>",
70 70 "url": "<url>",
71 71 "title": "<title>",
72 72 "description": "<description>",
73 73 "status" : "<status>",
74 74 "created_on": "<date_time_created>",
75 75 "updated_on": "<date_time_updated>",
76 76 "versions": "<number_or_versions_of_pr>",
77 77 "commit_ids": [
78 78 ...
79 79 "<commit_id>",
80 80 "<commit_id>",
81 81 ...
82 82 ],
83 83 "review_status": "<review_status>",
84 84 "mergeable": {
85 85 "status": "<bool>",
86 86 "message": "<message>",
87 87 },
88 88 "source": {
89 89 "clone_url": "<clone_url>",
90 90 "repository": "<repository_name>",
91 91 "reference":
92 92 {
93 93 "name": "<name>",
94 94 "type": "<type>",
95 95 "commit_id": "<commit_id>",
96 96 }
97 97 },
98 98 "target": {
99 99 "clone_url": "<clone_url>",
100 100 "repository": "<repository_name>",
101 101 "reference":
102 102 {
103 103 "name": "<name>",
104 104 "type": "<type>",
105 105 "commit_id": "<commit_id>",
106 106 }
107 107 },
108 108 "merge": {
109 109 "clone_url": "<clone_url>",
110 110 "reference":
111 111 {
112 112 "name": "<name>",
113 113 "type": "<type>",
114 114 "commit_id": "<commit_id>",
115 115 }
116 116 },
117 117 "author": <user_obj>,
118 118 "reviewers": [
119 119 ...
120 120 {
121 121 "user": "<user_obj>",
122 122 "review_status": "<review_status>",
123 123 }
124 124 ...
125 125 ]
126 126 },
127 127 "error": null
128 128 """
129 129
130 130 pull_request = get_pull_request_or_error(pullrequestid)
131 131 if Optional.extract(repoid):
132 132 repo = get_repo_or_error(repoid)
133 133 else:
134 134 repo = pull_request.target_repo
135 135
136 136 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
137 137 raise JSONRPCError('repository `%s` or pull request `%s` '
138 138 'does not exist' % (repoid, pullrequestid))
139 139
140 140 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
141 141 # otherwise we can lock the repo on calculation of merge state while update/merge
142 142 # is happening.
143 143 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
144 144 merge_state = Optional.extract(merge_state, binary=True) and pr_created
145 145 data = pull_request.get_api_data(with_merge_state=merge_state)
146 146 return data
147 147
148 148
149 149 @jsonrpc_method()
150 150 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
151 151 merge_state=Optional(False)):
152 152 """
153 153 Get all pull requests from the repository specified in `repoid`.
154 154
155 155 :param apiuser: This is filled automatically from the |authtoken|.
156 156 :type apiuser: AuthUser
157 157 :param repoid: Optional repository name or repository ID.
158 158 :type repoid: str or int
159 159 :param status: Only return pull requests with the specified status.
160 160 Valid options are.
161 161 * ``new`` (default)
162 162 * ``open``
163 163 * ``closed``
164 164 :type status: str
165 165 :param merge_state: Optional calculate merge state for each repository.
166 166 This could result in longer time to fetch the data
167 167 :type merge_state: bool
168 168
169 169 Example output:
170 170
171 171 .. code-block:: bash
172 172
173 173 "id": <id_given_in_input>,
174 174 "result":
175 175 [
176 176 ...
177 177 {
178 178 "pull_request_id": "<pull_request_id>",
179 179 "url": "<url>",
180 180 "title" : "<title>",
181 181 "description": "<description>",
182 182 "status": "<status>",
183 183 "created_on": "<date_time_created>",
184 184 "updated_on": "<date_time_updated>",
185 185 "commit_ids": [
186 186 ...
187 187 "<commit_id>",
188 188 "<commit_id>",
189 189 ...
190 190 ],
191 191 "review_status": "<review_status>",
192 192 "mergeable": {
193 193 "status": "<bool>",
194 194 "message: "<message>",
195 195 },
196 196 "source": {
197 197 "clone_url": "<clone_url>",
198 198 "reference":
199 199 {
200 200 "name": "<name>",
201 201 "type": "<type>",
202 202 "commit_id": "<commit_id>",
203 203 }
204 204 },
205 205 "target": {
206 206 "clone_url": "<clone_url>",
207 207 "reference":
208 208 {
209 209 "name": "<name>",
210 210 "type": "<type>",
211 211 "commit_id": "<commit_id>",
212 212 }
213 213 },
214 214 "merge": {
215 215 "clone_url": "<clone_url>",
216 216 "reference":
217 217 {
218 218 "name": "<name>",
219 219 "type": "<type>",
220 220 "commit_id": "<commit_id>",
221 221 }
222 222 },
223 223 "author": <user_obj>,
224 224 "reviewers": [
225 225 ...
226 226 {
227 227 "user": "<user_obj>",
228 228 "review_status": "<review_status>",
229 229 }
230 230 ...
231 231 ]
232 232 }
233 233 ...
234 234 ],
235 235 "error": null
236 236
237 237 """
238 238 repo = get_repo_or_error(repoid)
239 239 if not has_superadmin_permission(apiuser):
240 240 _perms = (
241 241 'repository.admin', 'repository.write', 'repository.read',)
242 242 validate_repo_permissions(apiuser, repoid, repo, _perms)
243 243
244 244 status = Optional.extract(status)
245 245 merge_state = Optional.extract(merge_state, binary=True)
246 246 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
247 247 order_by='id', order_dir='desc')
248 248 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
249 249 return data
250 250
251 251
252 252 @jsonrpc_method()
253 253 def merge_pull_request(
254 254 request, apiuser, pullrequestid, repoid=Optional(None),
255 255 userid=Optional(OAttr('apiuser'))):
256 256 """
257 257 Merge the pull request specified by `pullrequestid` into its target
258 258 repository.
259 259
260 260 :param apiuser: This is filled automatically from the |authtoken|.
261 261 :type apiuser: AuthUser
262 262 :param repoid: Optional, repository name or repository ID of the
263 263 target repository to which the |pr| is to be merged.
264 264 :type repoid: str or int
265 265 :param pullrequestid: ID of the pull request which shall be merged.
266 266 :type pullrequestid: int
267 267 :param userid: Merge the pull request as this user.
268 268 :type userid: Optional(str or int)
269 269
270 270 Example output:
271 271
272 272 .. code-block:: bash
273 273
274 274 "id": <id_given_in_input>,
275 275 "result": {
276 276 "executed": "<bool>",
277 277 "failure_reason": "<int>",
278 278 "merge_status_message": "<str>",
279 279 "merge_commit_id": "<merge_commit_id>",
280 280 "possible": "<bool>",
281 281 "merge_ref": {
282 282 "commit_id": "<commit_id>",
283 283 "type": "<type>",
284 284 "name": "<name>"
285 285 }
286 286 },
287 287 "error": null
288 288 """
289 289 pull_request = get_pull_request_or_error(pullrequestid)
290 290 if Optional.extract(repoid):
291 291 repo = get_repo_or_error(repoid)
292 292 else:
293 293 repo = pull_request.target_repo
294 294 auth_user = apiuser
295 295 if not isinstance(userid, Optional):
296 296 if (has_superadmin_permission(apiuser) or
297 297 HasRepoPermissionAnyApi('repository.admin')(
298 298 user=apiuser, repo_name=repo.repo_name)):
299 299 apiuser = get_user_or_error(userid)
300 300 auth_user = apiuser.AuthUser()
301 301 else:
302 302 raise JSONRPCError('userid is not the same as your user')
303 303
304 304 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
305 305 raise JSONRPCError(
306 306 'Operation forbidden because pull request is in state {}, '
307 307 'only state {} is allowed.'.format(
308 308 pull_request.pull_request_state, PullRequest.STATE_CREATED))
309 309
310 310 with pull_request.set_state(PullRequest.STATE_UPDATING):
311 311 check = MergeCheck.validate(pull_request, auth_user=auth_user,
312 312 translator=request.translate)
313 313 merge_possible = not check.failed
314 314
315 315 if not merge_possible:
316 316 error_messages = []
317 317 for err_type, error_msg in check.errors:
318 318 error_msg = request.translate(error_msg)
319 319 error_messages.append(error_msg)
320 320
321 321 reasons = ','.join(error_messages)
322 322 raise JSONRPCError(
323 323 'merge not possible for following reasons: {}'.format(reasons))
324 324
325 325 target_repo = pull_request.target_repo
326 326 extras = vcs_operation_context(
327 327 request.environ, repo_name=target_repo.repo_name,
328 328 username=auth_user.username, action='push',
329 329 scm=target_repo.repo_type)
330 330 with pull_request.set_state(PullRequest.STATE_UPDATING):
331 331 merge_response = PullRequestModel().merge_repo(
332 332 pull_request, apiuser, extras=extras)
333 333 if merge_response.executed:
334 334 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
335 335
336 336 Session().commit()
337 337
338 338 # In previous versions the merge response directly contained the merge
339 339 # commit id. It is now contained in the merge reference object. To be
340 340 # backwards compatible we have to extract it again.
341 341 merge_response = merge_response.asdict()
342 342 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
343 343
344 344 return merge_response
345 345
346 346
347 347 @jsonrpc_method()
348 348 def get_pull_request_comments(
349 349 request, apiuser, pullrequestid, repoid=Optional(None)):
350 350 """
351 351 Get all comments of pull request specified with the `pullrequestid`
352 352
353 353 :param apiuser: This is filled automatically from the |authtoken|.
354 354 :type apiuser: AuthUser
355 355 :param repoid: Optional repository name or repository ID.
356 356 :type repoid: str or int
357 357 :param pullrequestid: The pull request ID.
358 358 :type pullrequestid: int
359 359
360 360 Example output:
361 361
362 362 .. code-block:: bash
363 363
364 364 id : <id_given_in_input>
365 365 result : [
366 366 {
367 367 "comment_author": {
368 368 "active": true,
369 369 "full_name_or_username": "Tom Gore",
370 370 "username": "admin"
371 371 },
372 372 "comment_created_on": "2017-01-02T18:43:45.533",
373 373 "comment_f_path": null,
374 374 "comment_id": 25,
375 375 "comment_lineno": null,
376 376 "comment_status": {
377 377 "status": "under_review",
378 378 "status_lbl": "Under Review"
379 379 },
380 380 "comment_text": "Example text",
381 381 "comment_type": null,
382 382 "pull_request_version": null,
383 383 "comment_commit_id": None,
384 384 "comment_pull_request_id": <pull_request_id>
385 385 }
386 386 ],
387 387 error : null
388 388 """
389 389
390 390 pull_request = get_pull_request_or_error(pullrequestid)
391 391 if Optional.extract(repoid):
392 392 repo = get_repo_or_error(repoid)
393 393 else:
394 394 repo = pull_request.target_repo
395 395
396 396 if not PullRequestModel().check_user_read(
397 397 pull_request, apiuser, api=True):
398 398 raise JSONRPCError('repository `%s` or pull request `%s` '
399 399 'does not exist' % (repoid, pullrequestid))
400 400
401 401 (pull_request_latest,
402 402 pull_request_at_ver,
403 403 pull_request_display_obj,
404 404 at_version) = PullRequestModel().get_pr_version(
405 405 pull_request.pull_request_id, version=None)
406 406
407 407 versions = pull_request_display_obj.versions()
408 408 ver_map = {
409 409 ver.pull_request_version_id: cnt
410 410 for cnt, ver in enumerate(versions, 1)
411 411 }
412 412
413 413 # GENERAL COMMENTS with versions #
414 414 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
415 415 q = q.order_by(ChangesetComment.comment_id.asc())
416 416 general_comments = q.all()
417 417
418 418 # INLINE COMMENTS with versions #
419 419 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
420 420 q = q.order_by(ChangesetComment.comment_id.asc())
421 421 inline_comments = q.all()
422 422
423 423 data = []
424 424 for comment in inline_comments + general_comments:
425 425 full_data = comment.get_api_data()
426 426 pr_version_id = None
427 427 if comment.pull_request_version_id:
428 428 pr_version_id = 'v{}'.format(
429 429 ver_map[comment.pull_request_version_id])
430 430
431 431 # sanitize some entries
432 432
433 433 full_data['pull_request_version'] = pr_version_id
434 434 full_data['comment_author'] = {
435 435 'username': full_data['comment_author'].username,
436 436 'full_name_or_username': full_data['comment_author'].full_name_or_username,
437 437 'active': full_data['comment_author'].active,
438 438 }
439 439
440 440 if full_data['comment_status']:
441 441 full_data['comment_status'] = {
442 442 'status': full_data['comment_status'][0].status,
443 443 'status_lbl': full_data['comment_status'][0].status_lbl,
444 444 }
445 445 else:
446 446 full_data['comment_status'] = {}
447 447
448 448 data.append(full_data)
449 449 return data
450 450
451 451
452 452 @jsonrpc_method()
453 453 def comment_pull_request(
454 454 request, apiuser, pullrequestid, repoid=Optional(None),
455 455 message=Optional(None), commit_id=Optional(None), status=Optional(None),
456 456 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
457 457 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
458 458 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
459 459 """
460 460 Comment on the pull request specified with the `pullrequestid`,
461 461 in the |repo| specified by the `repoid`, and optionally change the
462 462 review status.
463 463
464 464 :param apiuser: This is filled automatically from the |authtoken|.
465 465 :type apiuser: AuthUser
466 466 :param repoid: Optional repository name or repository ID.
467 467 :type repoid: str or int
468 468 :param pullrequestid: The pull request ID.
469 469 :type pullrequestid: int
470 470 :param commit_id: Specify the commit_id for which to set a comment. If
471 471 given commit_id is different than latest in the PR status
472 472 change won't be performed.
473 473 :type commit_id: str
474 474 :param message: The text content of the comment.
475 475 :type message: str
476 476 :param status: (**Optional**) Set the approval status of the pull
477 477 request. One of: 'not_reviewed', 'approved', 'rejected',
478 478 'under_review'
479 479 :type status: str
480 480 :param comment_type: Comment type, one of: 'note', 'todo'
481 481 :type comment_type: Optional(str), default: 'note'
482 482 :param resolves_comment_id: id of comment which this one will resolve
483 483 :type resolves_comment_id: Optional(int)
484 484 :param extra_recipients: list of user ids or usernames to add
485 485 notifications for this comment. Acts like a CC for notification
486 486 :type extra_recipients: Optional(list)
487 487 :param userid: Comment on the pull request as this user
488 488 :type userid: Optional(str or int)
489 489 :param send_email: Define if this comment should also send email notification
490 490 :type send_email: Optional(bool)
491 491
492 492 Example output:
493 493
494 494 .. code-block:: bash
495 495
496 496 id : <id_given_in_input>
497 497 result : {
498 498 "pull_request_id": "<Integer>",
499 499 "comment_id": "<Integer>",
500 500 "status": {"given": <given_status>,
501 501 "was_changed": <bool status_was_actually_changed> },
502 502 },
503 503 error : null
504 504 """
505 505 pull_request = get_pull_request_or_error(pullrequestid)
506 506 if Optional.extract(repoid):
507 507 repo = get_repo_or_error(repoid)
508 508 else:
509 509 repo = pull_request.target_repo
510 510
511 511 auth_user = apiuser
512 512 if not isinstance(userid, Optional):
513 513 if (has_superadmin_permission(apiuser) or
514 514 HasRepoPermissionAnyApi('repository.admin')(
515 515 user=apiuser, repo_name=repo.repo_name)):
516 516 apiuser = get_user_or_error(userid)
517 517 auth_user = apiuser.AuthUser()
518 518 else:
519 519 raise JSONRPCError('userid is not the same as your user')
520 520
521 521 if pull_request.is_closed():
522 522 raise JSONRPCError(
523 523 'pull request `%s` comment failed, pull request is closed' % (
524 524 pullrequestid,))
525 525
526 526 if not PullRequestModel().check_user_read(
527 527 pull_request, apiuser, api=True):
528 528 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
529 529 message = Optional.extract(message)
530 530 status = Optional.extract(status)
531 531 commit_id = Optional.extract(commit_id)
532 532 comment_type = Optional.extract(comment_type)
533 533 resolves_comment_id = Optional.extract(resolves_comment_id)
534 534 extra_recipients = Optional.extract(extra_recipients)
535 535 send_email = Optional.extract(send_email, binary=True)
536 536
537 537 if not message and not status:
538 538 raise JSONRPCError(
539 539 'Both message and status parameters are missing. '
540 540 'At least one is required.')
541 541
542 542 if (status not in (st[0] for st in ChangesetStatus.STATUSES) and
543 543 status is not None):
544 544 raise JSONRPCError('Unknown comment status: `%s`' % status)
545 545
546 546 if commit_id and commit_id not in pull_request.revisions:
547 547 raise JSONRPCError(
548 548 'Invalid commit_id `%s` for this pull request.' % commit_id)
549 549
550 550 allowed_to_change_status = PullRequestModel().check_user_change_status(
551 551 pull_request, apiuser)
552 552
553 553 # if commit_id is passed re-validated if user is allowed to change status
554 554 # based on latest commit_id from the PR
555 555 if commit_id:
556 556 commit_idx = pull_request.revisions.index(commit_id)
557 557 if commit_idx != 0:
558 558 allowed_to_change_status = False
559 559
560 560 if resolves_comment_id:
561 561 comment = ChangesetComment.get(resolves_comment_id)
562 562 if not comment:
563 563 raise JSONRPCError(
564 564 'Invalid resolves_comment_id `%s` for this pull request.'
565 565 % resolves_comment_id)
566 566 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
567 567 raise JSONRPCError(
568 568 'Comment `%s` is wrong type for setting status to resolved.'
569 569 % resolves_comment_id)
570 570
571 571 text = message
572 572 status_label = ChangesetStatus.get_status_lbl(status)
573 573 if status and allowed_to_change_status:
574 574 st_message = ('Status change %(transition_icon)s %(status)s'
575 575 % {'transition_icon': '>', 'status': status_label})
576 576 text = message or st_message
577 577
578 578 rc_config = SettingsModel().get_all_settings()
579 579 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
580 580
581 581 status_change = status and allowed_to_change_status
582 582 comment = CommentsModel().create(
583 583 text=text,
584 584 repo=pull_request.target_repo.repo_id,
585 585 user=apiuser.user_id,
586 586 pull_request=pull_request.pull_request_id,
587 587 f_path=None,
588 588 line_no=None,
589 589 status_change=(status_label if status_change else None),
590 590 status_change_type=(status if status_change else None),
591 591 closing_pr=False,
592 592 renderer=renderer,
593 593 comment_type=comment_type,
594 594 resolves_comment_id=resolves_comment_id,
595 595 auth_user=auth_user,
596 596 extra_recipients=extra_recipients,
597 597 send_email=send_email
598 598 )
599 599
600 600 if allowed_to_change_status and status:
601 601 old_calculated_status = pull_request.calculated_review_status()
602 602 ChangesetStatusModel().set_status(
603 603 pull_request.target_repo.repo_id,
604 604 status,
605 605 apiuser.user_id,
606 606 comment,
607 607 pull_request=pull_request.pull_request_id
608 608 )
609 609 Session().flush()
610 610
611 611 Session().commit()
612 612
613 613 PullRequestModel().trigger_pull_request_hook(
614 614 pull_request, apiuser, 'comment',
615 615 data={'comment': comment})
616 616
617 617 if allowed_to_change_status and status:
618 618 # we now calculate the status of pull request, and based on that
619 619 # calculation we set the commits status
620 620 calculated_status = pull_request.calculated_review_status()
621 621 if old_calculated_status != calculated_status:
622 622 PullRequestModel().trigger_pull_request_hook(
623 623 pull_request, apiuser, 'review_status_change',
624 624 data={'status': calculated_status})
625 625
626 626 data = {
627 627 'pull_request_id': pull_request.pull_request_id,
628 628 'comment_id': comment.comment_id if comment else None,
629 629 'status': {'given': status, 'was_changed': status_change},
630 630 }
631 631 return data
632 632
633 633
634 634 @jsonrpc_method()
635 635 def create_pull_request(
636 636 request, apiuser, source_repo, target_repo, source_ref, target_ref,
637 637 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
638 638 description_renderer=Optional(''), reviewers=Optional(None)):
639 639 """
640 640 Creates a new pull request.
641 641
642 642 Accepts refs in the following formats:
643 643
644 644 * branch:<branch_name>:<sha>
645 645 * branch:<branch_name>
646 646 * bookmark:<bookmark_name>:<sha> (Mercurial only)
647 647 * bookmark:<bookmark_name> (Mercurial only)
648 648
649 649 :param apiuser: This is filled automatically from the |authtoken|.
650 650 :type apiuser: AuthUser
651 651 :param source_repo: Set the source repository name.
652 652 :type source_repo: str
653 653 :param target_repo: Set the target repository name.
654 654 :type target_repo: str
655 655 :param source_ref: Set the source ref name.
656 656 :type source_ref: str
657 657 :param target_ref: Set the target ref name.
658 658 :type target_ref: str
659 659 :param owner: user_id or username
660 660 :type owner: Optional(str)
661 661 :param title: Optionally Set the pull request title, it's generated otherwise
662 662 :type title: str
663 663 :param description: Set the pull request description.
664 664 :type description: Optional(str)
665 665 :type description_renderer: Optional(str)
666 666 :param description_renderer: Set pull request renderer for the description.
667 667 It should be 'rst', 'markdown' or 'plain'. If not give default
668 668 system renderer will be used
669 669 :param reviewers: Set the new pull request reviewers list.
670 670 Reviewer defined by review rules will be added automatically to the
671 671 defined list.
672 672 :type reviewers: Optional(list)
673 673 Accepts username strings or objects of the format:
674 674
675 675 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
676 676 """
677 677
678 678 source_db_repo = get_repo_or_error(source_repo)
679 679 target_db_repo = get_repo_or_error(target_repo)
680 680 if not has_superadmin_permission(apiuser):
681 681 _perms = ('repository.admin', 'repository.write', 'repository.read',)
682 682 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
683 683
684 684 owner = validate_set_owner_permissions(apiuser, owner)
685 685
686 686 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
687 687 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
688 688
689 689 source_scm = source_db_repo.scm_instance()
690 690 target_scm = target_db_repo.scm_instance()
691 691
692 692 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
693 693 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
694 694
695 695 ancestor = source_scm.get_common_ancestor(
696 696 source_commit.raw_id, target_commit.raw_id, target_scm)
697 697 if not ancestor:
698 698 raise JSONRPCError('no common ancestor found')
699 699
700 700 # recalculate target ref based on ancestor
701 701 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
702 702 full_target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
703 703
704 704 commit_ranges = target_scm.compare(
705 705 target_commit.raw_id, source_commit.raw_id, source_scm,
706 706 merge=True, pre_load=[])
707 707
708 708 if not commit_ranges:
709 709 raise JSONRPCError('no commits found')
710 710
711 711 reviewer_objects = Optional.extract(reviewers) or []
712 712
713 713 # serialize and validate passed in given reviewers
714 714 if reviewer_objects:
715 715 schema = ReviewerListSchema()
716 716 try:
717 717 reviewer_objects = schema.deserialize(reviewer_objects)
718 718 except Invalid as err:
719 719 raise JSONRPCValidationError(colander_exc=err)
720 720
721 721 # validate users
722 722 for reviewer_object in reviewer_objects:
723 723 user = get_user_or_error(reviewer_object['username'])
724 724 reviewer_object['user_id'] = user.user_id
725 725
726 726 get_default_reviewers_data, validate_default_reviewers = \
727 727 PullRequestModel().get_reviewer_functions()
728 728
729 729 # recalculate reviewers logic, to make sure we can validate this
730 730 reviewer_rules = get_default_reviewers_data(
731 731 owner, source_db_repo,
732 732 source_commit, target_db_repo, target_commit)
733 733
734 734 # now MERGE our given with the calculated
735 735 reviewer_objects = reviewer_rules['reviewers'] + reviewer_objects
736 736
737 737 try:
738 738 reviewers = validate_default_reviewers(
739 739 reviewer_objects, reviewer_rules)
740 740 except ValueError as e:
741 741 raise JSONRPCError('Reviewers Validation: {}'.format(e))
742 742
743 743 title = Optional.extract(title)
744 744 if not title:
745 745 title_source_ref = source_ref.split(':', 2)[1]
746 746 title = PullRequestModel().generate_pullrequest_title(
747 747 source=source_repo,
748 748 source_ref=title_source_ref,
749 749 target=target_repo
750 750 )
751 751 # fetch renderer, if set fallback to plain in case of PR
752 752 rc_config = SettingsModel().get_all_settings()
753 753 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
754 754 description = Optional.extract(description)
755 755 description_renderer = Optional.extract(description_renderer) or default_system_renderer
756 756
757 757 pull_request = PullRequestModel().create(
758 758 created_by=owner.user_id,
759 759 source_repo=source_repo,
760 760 source_ref=full_source_ref,
761 761 target_repo=target_repo,
762 762 target_ref=full_target_ref,
763 763 revisions=[commit.raw_id for commit in reversed(commit_ranges)],
764 764 reviewers=reviewers,
765 765 title=title,
766 766 description=description,
767 767 description_renderer=description_renderer,
768 768 reviewer_data=reviewer_rules,
769 769 auth_user=apiuser
770 770 )
771 771
772 772 Session().commit()
773 773 data = {
774 774 'msg': 'Created new pull request `{}`'.format(title),
775 775 'pull_request_id': pull_request.pull_request_id,
776 776 }
777 777 return data
778 778
779 779
780 780 @jsonrpc_method()
781 781 def update_pull_request(
782 782 request, apiuser, pullrequestid, repoid=Optional(None),
783 783 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
784 784 reviewers=Optional(None), update_commits=Optional(None)):
785 785 """
786 786 Updates a pull request.
787 787
788 788 :param apiuser: This is filled automatically from the |authtoken|.
789 789 :type apiuser: AuthUser
790 790 :param repoid: Optional repository name or repository ID.
791 791 :type repoid: str or int
792 792 :param pullrequestid: The pull request ID.
793 793 :type pullrequestid: int
794 794 :param title: Set the pull request title.
795 795 :type title: str
796 796 :param description: Update pull request description.
797 797 :type description: Optional(str)
798 798 :type description_renderer: Optional(str)
799 799 :param description_renderer: Update pull request renderer for the description.
800 800 It should be 'rst', 'markdown' or 'plain'
801 801 :param reviewers: Update pull request reviewers list with new value.
802 802 :type reviewers: Optional(list)
803 803 Accepts username strings or objects of the format:
804 804
805 805 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
806 806
807 807 :param update_commits: Trigger update of commits for this pull request
808 808 :type: update_commits: Optional(bool)
809 809
810 810 Example output:
811 811
812 812 .. code-block:: bash
813 813
814 814 id : <id_given_in_input>
815 815 result : {
816 816 "msg": "Updated pull request `63`",
817 817 "pull_request": <pull_request_object>,
818 818 "updated_reviewers": {
819 819 "added": [
820 820 "username"
821 821 ],
822 822 "removed": []
823 823 },
824 824 "updated_commits": {
825 825 "added": [
826 826 "<sha1_hash>"
827 827 ],
828 828 "common": [
829 829 "<sha1_hash>",
830 830 "<sha1_hash>",
831 831 ],
832 832 "removed": []
833 833 }
834 834 }
835 835 error : null
836 836 """
837 837
838 838 pull_request = get_pull_request_or_error(pullrequestid)
839 839 if Optional.extract(repoid):
840 840 repo = get_repo_or_error(repoid)
841 841 else:
842 842 repo = pull_request.target_repo
843 843
844 844 if not PullRequestModel().check_user_update(
845 845 pull_request, apiuser, api=True):
846 846 raise JSONRPCError(
847 847 'pull request `%s` update failed, no permission to update.' % (
848 848 pullrequestid,))
849 849 if pull_request.is_closed():
850 850 raise JSONRPCError(
851 851 'pull request `%s` update failed, pull request is closed' % (
852 852 pullrequestid,))
853 853
854 854 reviewer_objects = Optional.extract(reviewers) or []
855 855
856 856 if reviewer_objects:
857 857 schema = ReviewerListSchema()
858 858 try:
859 859 reviewer_objects = schema.deserialize(reviewer_objects)
860 860 except Invalid as err:
861 861 raise JSONRPCValidationError(colander_exc=err)
862 862
863 863 # validate users
864 864 for reviewer_object in reviewer_objects:
865 865 user = get_user_or_error(reviewer_object['username'])
866 866 reviewer_object['user_id'] = user.user_id
867 867
868 868 get_default_reviewers_data, get_validated_reviewers = \
869 869 PullRequestModel().get_reviewer_functions()
870 870
871 871 # re-use stored rules
872 872 reviewer_rules = pull_request.reviewer_data
873 873 try:
874 874 reviewers = get_validated_reviewers(
875 875 reviewer_objects, reviewer_rules)
876 876 except ValueError as e:
877 877 raise JSONRPCError('Reviewers Validation: {}'.format(e))
878 878 else:
879 879 reviewers = []
880 880
881 881 title = Optional.extract(title)
882 882 description = Optional.extract(description)
883 883 description_renderer = Optional.extract(description_renderer)
884 884
885 885 if title or description:
886 886 PullRequestModel().edit(
887 887 pull_request,
888 888 title or pull_request.title,
889 889 description or pull_request.description,
890 890 description_renderer or pull_request.description_renderer,
891 891 apiuser)
892 892 Session().commit()
893 893
894 894 commit_changes = {"added": [], "common": [], "removed": []}
895 895 if str2bool(Optional.extract(update_commits)):
896 896
897 897 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
898 898 raise JSONRPCError(
899 899 'Operation forbidden because pull request is in state {}, '
900 900 'only state {} is allowed.'.format(
901 901 pull_request.pull_request_state, PullRequest.STATE_CREATED))
902 902
903 903 with pull_request.set_state(PullRequest.STATE_UPDATING):
904 904 if PullRequestModel().has_valid_update_type(pull_request):
905 905 db_user = apiuser.get_instance()
906 906 update_response = PullRequestModel().update_commits(
907 907 pull_request, db_user)
908 908 commit_changes = update_response.changes or commit_changes
909 909 Session().commit()
910 910
911 911 reviewers_changes = {"added": [], "removed": []}
912 912 if reviewers:
913 913 old_calculated_status = pull_request.calculated_review_status()
914 914 added_reviewers, removed_reviewers = \
915 915 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser)
916 916
917 917 reviewers_changes['added'] = sorted(
918 918 [get_user_or_error(n).username for n in added_reviewers])
919 919 reviewers_changes['removed'] = sorted(
920 920 [get_user_or_error(n).username for n in removed_reviewers])
921 921 Session().commit()
922 922
923 923 # trigger status changed if change in reviewers changes the status
924 924 calculated_status = pull_request.calculated_review_status()
925 925 if old_calculated_status != calculated_status:
926 926 PullRequestModel().trigger_pull_request_hook(
927 927 pull_request, apiuser, 'review_status_change',
928 928 data={'status': calculated_status})
929 929
930 930 data = {
931 931 'msg': 'Updated pull request `{}`'.format(
932 932 pull_request.pull_request_id),
933 933 'pull_request': pull_request.get_api_data(),
934 934 'updated_commits': commit_changes,
935 935 'updated_reviewers': reviewers_changes
936 936 }
937 937
938 938 return data
939 939
940 940
941 941 @jsonrpc_method()
942 942 def close_pull_request(
943 943 request, apiuser, pullrequestid, repoid=Optional(None),
944 944 userid=Optional(OAttr('apiuser')), message=Optional('')):
945 945 """
946 946 Close the pull request specified by `pullrequestid`.
947 947
948 948 :param apiuser: This is filled automatically from the |authtoken|.
949 949 :type apiuser: AuthUser
950 950 :param repoid: Repository name or repository ID to which the pull
951 951 request belongs.
952 952 :type repoid: str or int
953 953 :param pullrequestid: ID of the pull request to be closed.
954 954 :type pullrequestid: int
955 955 :param userid: Close the pull request as this user.
956 956 :type userid: Optional(str or int)
957 957 :param message: Optional message to close the Pull Request with. If not
958 958 specified it will be generated automatically.
959 959 :type message: Optional(str)
960 960
961 961 Example output:
962 962
963 963 .. code-block:: bash
964 964
965 965 "id": <id_given_in_input>,
966 966 "result": {
967 967 "pull_request_id": "<int>",
968 968 "close_status": "<str:status_lbl>,
969 969 "closed": "<bool>"
970 970 },
971 971 "error": null
972 972
973 973 """
974 974 _ = request.translate
975 975
976 976 pull_request = get_pull_request_or_error(pullrequestid)
977 977 if Optional.extract(repoid):
978 978 repo = get_repo_or_error(repoid)
979 979 else:
980 980 repo = pull_request.target_repo
981 981
982 982 if not isinstance(userid, Optional):
983 983 if (has_superadmin_permission(apiuser) or
984 984 HasRepoPermissionAnyApi('repository.admin')(
985 985 user=apiuser, repo_name=repo.repo_name)):
986 986 apiuser = get_user_or_error(userid)
987 987 else:
988 988 raise JSONRPCError('userid is not the same as your user')
989 989
990 990 if pull_request.is_closed():
991 991 raise JSONRPCError(
992 992 'pull request `%s` is already closed' % (pullrequestid,))
993 993
994 994 # only owner or admin or person with write permissions
995 995 allowed_to_close = PullRequestModel().check_user_update(
996 996 pull_request, apiuser, api=True)
997 997
998 998 if not allowed_to_close:
999 999 raise JSONRPCError(
1000 1000 'pull request `%s` close failed, no permission to close.' % (
1001 1001 pullrequestid,))
1002 1002
1003 1003 # message we're using to close the PR, else it's automatically generated
1004 1004 message = Optional.extract(message)
1005 1005
1006 1006 # finally close the PR, with proper message comment
1007 1007 comment, status = PullRequestModel().close_pull_request_with_comment(
1008 1008 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1009 1009 status_lbl = ChangesetStatus.get_status_lbl(status)
1010 1010
1011 1011 Session().commit()
1012 1012
1013 1013 data = {
1014 1014 'pull_request_id': pull_request.pull_request_id,
1015 1015 'close_status': status_lbl,
1016 1016 'closed': True,
1017 1017 }
1018 1018 return data
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was 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