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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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-20 |
|
|
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