Show More
The requested changes are too big and content was truncated. Show full diff
@@ -1,58 +1,58 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest # noqa |
|
21 | 21 | |
|
22 | 22 | # keep the imports to have a toplevel conftest.py but still importable from EE edition |
|
23 | 23 | from rhodecode.tests.conftest_common import ( # noqa |
|
24 | 24 | pytest_generate_tests, |
|
25 | 25 | pytest_runtest_makereport, |
|
26 | 26 | pytest_addoption |
|
27 | 27 | ) |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | pytest_plugins = [ |
|
31 | 31 | "rhodecode.tests.fixture_mods.fixture_pyramid", |
|
32 | 32 | "rhodecode.tests.fixture_mods.fixture_utils", |
|
33 | 33 | ] |
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | def pytest_configure(config): |
|
37 | 37 | from rhodecode.config import patches # noqa |
|
38 | 38 | |
|
39 | 39 | |
|
40 | 40 | def pytest_collection_modifyitems(session, config, items): |
|
41 | 41 | # nottest marked, compare nose, used for transition from nose to pytest |
|
42 | 42 | remaining = [ |
|
43 | 43 | i for i in items if getattr(i.obj, '__test__', True)] |
|
44 | 44 | items[:] = remaining |
|
45 | 45 | |
|
46 | 46 | # NOTE(marcink): custom test ordering, db tests and vcstests are slowest and should |
|
47 | 47 | # be executed at the end for faster test feedback |
|
48 | 48 | def sorter(item): |
|
49 | 49 | pos = 0 |
|
50 | 50 | key = item._nodeid |
|
51 | 51 | if key.startswith('rhodecode/tests/database'): |
|
52 | 52 | pos = 1 |
|
53 | 53 | elif key.startswith('rhodecode/tests/vcs_operations'): |
|
54 | 54 | pos = 2 |
|
55 | 55 | |
|
56 | 56 | return pos |
|
57 | 57 | |
|
58 | 58 | items.sort(key=sorter) |
@@ -1,88 +1,88 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import os |
|
21 | 21 | import datetime |
|
22 | 22 | import collections |
|
23 | 23 | |
|
24 | 24 | now = datetime.datetime.now() |
|
25 | 25 | now = now.strftime("%Y-%m-%d %H:%M:%S") + '.' + "{:03d}".format(int(now.microsecond/1000)) |
|
26 | 26 | |
|
27 | 27 | print(f'{now} Starting RhodeCode imports...') |
|
28 | 28 | |
|
29 | 29 | VERSION = tuple(open(os.path.join( |
|
30 | 30 | os.path.dirname(__file__), 'VERSION')).read().split('.')) |
|
31 | 31 | |
|
32 | 32 | BACKENDS = collections.OrderedDict() |
|
33 | 33 | |
|
34 | 34 | BACKENDS['hg'] = 'Mercurial repository' |
|
35 | 35 | BACKENDS['git'] = 'Git repository' |
|
36 | 36 | BACKENDS['svn'] = 'Subversion repository' |
|
37 | 37 | |
|
38 | 38 | |
|
39 | 39 | CELERY_ENABLED = False |
|
40 | 40 | CELERY_EAGER = False |
|
41 | 41 | |
|
42 | 42 | # link to config for pyramid |
|
43 | 43 | CONFIG = {} |
|
44 | 44 | |
|
45 | 45 | |
|
46 | 46 | class ConfigGet: |
|
47 | 47 | NotGiven = object() |
|
48 | 48 | |
|
49 | 49 | def _get_val_or_missing(self, key, missing): |
|
50 | 50 | if key not in CONFIG: |
|
51 | 51 | if missing == self.NotGiven: |
|
52 | 52 | return missing |
|
53 | 53 | # we don't get key, we don't get missing value, return nothing similar as config.get(key) |
|
54 | 54 | return None |
|
55 | 55 | else: |
|
56 | 56 | val = CONFIG[key] |
|
57 | 57 | return val |
|
58 | 58 | |
|
59 | 59 | def get_str(self, key, missing=NotGiven): |
|
60 | 60 | from rhodecode.lib.str_utils import safe_str |
|
61 | 61 | val = self._get_val_or_missing(key, missing) |
|
62 | 62 | return safe_str(val) |
|
63 | 63 | |
|
64 | 64 | def get_int(self, key, missing=NotGiven): |
|
65 | 65 | from rhodecode.lib.str_utils import safe_int |
|
66 | 66 | val = self._get_val_or_missing(key, missing) |
|
67 | 67 | return safe_int(val) |
|
68 | 68 | |
|
69 | 69 | def get_bool(self, key, missing=NotGiven): |
|
70 | 70 | from rhodecode.lib.type_utils import str2bool |
|
71 | 71 | val = self._get_val_or_missing(key, missing) |
|
72 | 72 | return str2bool(val) |
|
73 | 73 | |
|
74 | 74 | # Populated with the settings dictionary from application init in |
|
75 | 75 | # rhodecode.conf.environment.load_pyramid_environment |
|
76 | 76 | PYRAMID_SETTINGS = {} |
|
77 | 77 | |
|
78 | 78 | # Linked module for extensions |
|
79 | 79 | EXTENSIONS = {} |
|
80 | 80 | |
|
81 | 81 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
82 | 82 | __dbversion__ = 114 # defines current db version for migrations |
|
83 | 83 | __license__ = 'AGPLv3, and Commercial License' |
|
84 | 84 | __author__ = 'RhodeCode GmbH' |
|
85 | 85 | __url__ = 'https://code.rhodecode.com' |
|
86 | 86 | |
|
87 | 87 | is_test = False |
|
88 | 88 | disable_error_handler = False |
@@ -1,576 +1,576 b'' | |||
|
1 | 1 | |
|
2 | 2 | |
|
3 |
# Copyright (C) 2011-202 |
|
|
3 | # Copyright (C) 2011-2023 RhodeCode GmbH | |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import itertools |
|
22 | 22 | import logging |
|
23 | 23 | import sys |
|
24 | 24 | import fnmatch |
|
25 | 25 | |
|
26 | 26 | import decorator |
|
27 | 27 | import typing |
|
28 | 28 | import venusian |
|
29 | 29 | from collections import OrderedDict |
|
30 | 30 | |
|
31 | 31 | from pyramid.exceptions import ConfigurationError |
|
32 | 32 | from pyramid.renderers import render |
|
33 | 33 | from pyramid.response import Response |
|
34 | 34 | from pyramid.httpexceptions import HTTPNotFound |
|
35 | 35 | |
|
36 | 36 | from rhodecode.api.exc import ( |
|
37 | 37 | JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError) |
|
38 | 38 | from rhodecode.apps._base import TemplateArgs |
|
39 | 39 | from rhodecode.lib.auth import AuthUser |
|
40 | 40 | from rhodecode.lib.base import get_ip_addr, attach_context_attributes |
|
41 | 41 | from rhodecode.lib.exc_tracking import store_exception |
|
42 | 42 | from rhodecode.lib import ext_json |
|
43 | 43 | from rhodecode.lib.utils2 import safe_str |
|
44 | 44 | from rhodecode.lib.plugins.utils import get_plugin_settings |
|
45 | 45 | from rhodecode.model.db import User, UserApiKeys |
|
46 | 46 | |
|
47 | 47 | log = logging.getLogger(__name__) |
|
48 | 48 | |
|
49 | 49 | DEFAULT_RENDERER = 'jsonrpc_renderer' |
|
50 | 50 | DEFAULT_URL = '/_admin/apiv2' |
|
51 | 51 | |
|
52 | 52 | |
|
53 | 53 | def find_methods(jsonrpc_methods, pattern): |
|
54 | 54 | matches = OrderedDict() |
|
55 | 55 | if not isinstance(pattern, (list, tuple)): |
|
56 | 56 | pattern = [pattern] |
|
57 | 57 | |
|
58 | 58 | for single_pattern in pattern: |
|
59 | 59 | for method_name, method in jsonrpc_methods.items(): |
|
60 | 60 | if fnmatch.fnmatch(method_name, single_pattern): |
|
61 | 61 | matches[method_name] = method |
|
62 | 62 | return matches |
|
63 | 63 | |
|
64 | 64 | |
|
65 | 65 | class ExtJsonRenderer(object): |
|
66 | 66 | """ |
|
67 | 67 | Custom renderer that makes use of our ext_json lib |
|
68 | 68 | |
|
69 | 69 | """ |
|
70 | 70 | |
|
71 | 71 | def __init__(self): |
|
72 | 72 | self.serializer = ext_json.formatted_json |
|
73 | 73 | |
|
74 | 74 | def __call__(self, info): |
|
75 | 75 | """ Returns a plain JSON-encoded string with content-type |
|
76 | 76 | ``application/json``. The content-type may be overridden by |
|
77 | 77 | setting ``request.response.content_type``.""" |
|
78 | 78 | |
|
79 | 79 | def _render(value, system): |
|
80 | 80 | request = system.get('request') |
|
81 | 81 | if request is not None: |
|
82 | 82 | response = request.response |
|
83 | 83 | ct = response.content_type |
|
84 | 84 | if ct == response.default_content_type: |
|
85 | 85 | response.content_type = 'application/json' |
|
86 | 86 | |
|
87 | 87 | return self.serializer(value) |
|
88 | 88 | |
|
89 | 89 | return _render |
|
90 | 90 | |
|
91 | 91 | |
|
92 | 92 | def jsonrpc_response(request, result): |
|
93 | 93 | rpc_id = getattr(request, 'rpc_id', None) |
|
94 | 94 | |
|
95 | 95 | ret_value = '' |
|
96 | 96 | if rpc_id: |
|
97 | 97 | ret_value = {'id': rpc_id, 'result': result, 'error': None} |
|
98 | 98 | |
|
99 | 99 | # fetch deprecation warnings, and store it inside results |
|
100 | 100 | deprecation = getattr(request, 'rpc_deprecation', None) |
|
101 | 101 | if deprecation: |
|
102 | 102 | ret_value['DEPRECATION_WARNING'] = deprecation |
|
103 | 103 | |
|
104 | 104 | raw_body = render(DEFAULT_RENDERER, ret_value, request=request) |
|
105 | 105 | content_type = 'application/json' |
|
106 | 106 | content_type_header = 'Content-Type' |
|
107 | 107 | headers = { |
|
108 | 108 | content_type_header: content_type |
|
109 | 109 | } |
|
110 | 110 | return Response( |
|
111 | 111 | body=raw_body, |
|
112 | 112 | content_type=content_type, |
|
113 | 113 | headerlist=[(k, v) for k, v in headers.items()] |
|
114 | 114 | ) |
|
115 | 115 | |
|
116 | 116 | |
|
117 | 117 | def jsonrpc_error(request, message, retid=None, code: typing.Optional[int] = None, headers: typing.Optional[dict] = None): |
|
118 | 118 | """ |
|
119 | 119 | Generate a Response object with a JSON-RPC error body |
|
120 | 120 | """ |
|
121 | 121 | headers = headers or {} |
|
122 | 122 | content_type = 'application/json' |
|
123 | 123 | content_type_header = 'Content-Type' |
|
124 | 124 | if content_type_header not in headers: |
|
125 | 125 | headers[content_type_header] = content_type |
|
126 | 126 | |
|
127 | 127 | err_dict = {'id': retid, 'result': None, 'error': message} |
|
128 | 128 | raw_body = render(DEFAULT_RENDERER, err_dict, request=request) |
|
129 | 129 | |
|
130 | 130 | return Response( |
|
131 | 131 | body=raw_body, |
|
132 | 132 | status=code, |
|
133 | 133 | content_type=content_type, |
|
134 | 134 | headerlist=[(k, v) for k, v in headers.items()] |
|
135 | 135 | ) |
|
136 | 136 | |
|
137 | 137 | |
|
138 | 138 | def exception_view(exc, request): |
|
139 | 139 | rpc_id = getattr(request, 'rpc_id', None) |
|
140 | 140 | |
|
141 | 141 | if isinstance(exc, JSONRPCError): |
|
142 | 142 | fault_message = safe_str(exc.message) |
|
143 | 143 | log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message) |
|
144 | 144 | elif isinstance(exc, JSONRPCValidationError): |
|
145 | 145 | colander_exc = exc.colander_exception |
|
146 | 146 | # TODO(marcink): think maybe of nicer way to serialize errors ? |
|
147 | 147 | fault_message = colander_exc.asdict() |
|
148 | 148 | log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message) |
|
149 | 149 | elif isinstance(exc, JSONRPCForbidden): |
|
150 | 150 | fault_message = 'Access was denied to this resource.' |
|
151 | 151 | log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message) |
|
152 | 152 | elif isinstance(exc, HTTPNotFound): |
|
153 | 153 | method = request.rpc_method |
|
154 | 154 | log.debug('json-rpc method `%s` not found in list of ' |
|
155 | 155 | 'api calls: %s, rpc_id:%s', |
|
156 | 156 | method, list(request.registry.jsonrpc_methods.keys()), rpc_id) |
|
157 | 157 | |
|
158 | 158 | similar = 'none' |
|
159 | 159 | try: |
|
160 | 160 | similar_paterns = [f'*{x}*' for x in method.split('_')] |
|
161 | 161 | similar_found = find_methods( |
|
162 | 162 | request.registry.jsonrpc_methods, similar_paterns) |
|
163 | 163 | similar = ', '.join(similar_found.keys()) or similar |
|
164 | 164 | except Exception: |
|
165 | 165 | # make the whole above block safe |
|
166 | 166 | pass |
|
167 | 167 | |
|
168 | 168 | fault_message = "No such method: {}. Similar methods: {}".format( |
|
169 | 169 | method, similar) |
|
170 | 170 | else: |
|
171 | 171 | fault_message = 'undefined error' |
|
172 | 172 | exc_info = exc.exc_info() |
|
173 | 173 | store_exception(id(exc_info), exc_info, prefix='rhodecode-api') |
|
174 | 174 | |
|
175 | 175 | statsd = request.registry.statsd |
|
176 | 176 | if statsd: |
|
177 | 177 | exc_type = "{}.{}".format(exc.__class__.__module__, exc.__class__.__name__) |
|
178 | 178 | statsd.incr('rhodecode_exception_total', |
|
179 | 179 | tags=["exc_source:api", "type:{}".format(exc_type)]) |
|
180 | 180 | |
|
181 | 181 | return jsonrpc_error(request, fault_message, rpc_id) |
|
182 | 182 | |
|
183 | 183 | |
|
184 | 184 | def request_view(request): |
|
185 | 185 | """ |
|
186 | 186 | Main request handling method. It handles all logic to call a specific |
|
187 | 187 | exposed method |
|
188 | 188 | """ |
|
189 | 189 | # cython compatible inspect |
|
190 | 190 | from rhodecode.config.patches import inspect_getargspec |
|
191 | 191 | inspect = inspect_getargspec() |
|
192 | 192 | |
|
193 | 193 | # check if we can find this session using api_key, get_by_auth_token |
|
194 | 194 | # search not expired tokens only |
|
195 | 195 | try: |
|
196 | 196 | api_user = User.get_by_auth_token(request.rpc_api_key) |
|
197 | 197 | |
|
198 | 198 | if api_user is None: |
|
199 | 199 | return jsonrpc_error( |
|
200 | 200 | request, retid=request.rpc_id, message='Invalid API KEY') |
|
201 | 201 | |
|
202 | 202 | if not api_user.active: |
|
203 | 203 | return jsonrpc_error( |
|
204 | 204 | request, retid=request.rpc_id, |
|
205 | 205 | message='Request from this user not allowed') |
|
206 | 206 | |
|
207 | 207 | # check if we are allowed to use this IP |
|
208 | 208 | auth_u = AuthUser( |
|
209 | 209 | api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr) |
|
210 | 210 | if not auth_u.ip_allowed: |
|
211 | 211 | return jsonrpc_error( |
|
212 | 212 | request, retid=request.rpc_id, |
|
213 | 213 | message='Request from IP:%s not allowed' % ( |
|
214 | 214 | request.rpc_ip_addr,)) |
|
215 | 215 | else: |
|
216 | 216 | log.info('Access for IP:%s allowed', request.rpc_ip_addr) |
|
217 | 217 | |
|
218 | 218 | # register our auth-user |
|
219 | 219 | request.rpc_user = auth_u |
|
220 | 220 | request.environ['rc_auth_user_id'] = str(auth_u.user_id) |
|
221 | 221 | |
|
222 | 222 | # now check if token is valid for API |
|
223 | 223 | auth_token = request.rpc_api_key |
|
224 | 224 | token_match = api_user.authenticate_by_token( |
|
225 | 225 | auth_token, roles=[UserApiKeys.ROLE_API]) |
|
226 | 226 | invalid_token = not token_match |
|
227 | 227 | |
|
228 | 228 | log.debug('Checking if API KEY is valid with proper role') |
|
229 | 229 | if invalid_token: |
|
230 | 230 | return jsonrpc_error( |
|
231 | 231 | request, retid=request.rpc_id, |
|
232 | 232 | message='API KEY invalid or, has bad role for an API call') |
|
233 | 233 | |
|
234 | 234 | except Exception: |
|
235 | 235 | log.exception('Error on API AUTH') |
|
236 | 236 | return jsonrpc_error( |
|
237 | 237 | request, retid=request.rpc_id, message='Invalid API KEY') |
|
238 | 238 | |
|
239 | 239 | method = request.rpc_method |
|
240 | 240 | func = request.registry.jsonrpc_methods[method] |
|
241 | 241 | |
|
242 | 242 | # now that we have a method, add request._req_params to |
|
243 | 243 | # self.kargs and dispatch control to WGIController |
|
244 | 244 | |
|
245 | 245 | argspec = inspect.getargspec(func) |
|
246 | 246 | arglist = argspec[0] |
|
247 | 247 | defs = argspec[3] or [] |
|
248 | 248 | defaults = [type(a) for a in defs] |
|
249 | 249 | default_empty = type(NotImplemented) |
|
250 | 250 | |
|
251 | 251 | # kw arguments required by this method |
|
252 | 252 | func_kwargs = dict(itertools.zip_longest( |
|
253 | 253 | reversed(arglist), reversed(defaults), fillvalue=default_empty)) |
|
254 | 254 | |
|
255 | 255 | # This attribute will need to be first param of a method that uses |
|
256 | 256 | # api_key, which is translated to instance of user at that name |
|
257 | 257 | user_var = 'apiuser' |
|
258 | 258 | request_var = 'request' |
|
259 | 259 | |
|
260 | 260 | for arg in [user_var, request_var]: |
|
261 | 261 | if arg not in arglist: |
|
262 | 262 | return jsonrpc_error( |
|
263 | 263 | request, |
|
264 | 264 | retid=request.rpc_id, |
|
265 | 265 | message='This method [%s] does not support ' |
|
266 | 266 | 'required parameter `%s`' % (func.__name__, arg)) |
|
267 | 267 | |
|
268 | 268 | # get our arglist and check if we provided them as args |
|
269 | 269 | for arg, default in func_kwargs.items(): |
|
270 | 270 | if arg in [user_var, request_var]: |
|
271 | 271 | # user_var and request_var are pre-hardcoded parameters and we |
|
272 | 272 | # don't need to do any translation |
|
273 | 273 | continue |
|
274 | 274 | |
|
275 | 275 | # skip the required param check if it's default value is |
|
276 | 276 | # NotImplementedType (default_empty) |
|
277 | 277 | if default == default_empty and arg not in request.rpc_params: |
|
278 | 278 | return jsonrpc_error( |
|
279 | 279 | request, |
|
280 | 280 | retid=request.rpc_id, |
|
281 | 281 | message=('Missing non optional `%s` arg in JSON DATA' % arg) |
|
282 | 282 | ) |
|
283 | 283 | |
|
284 | 284 | # sanitize extra passed arguments |
|
285 | 285 | for k in list(request.rpc_params.keys()): |
|
286 | 286 | if k not in func_kwargs: |
|
287 | 287 | del request.rpc_params[k] |
|
288 | 288 | |
|
289 | 289 | call_params = request.rpc_params |
|
290 | 290 | call_params.update({ |
|
291 | 291 | 'request': request, |
|
292 | 292 | 'apiuser': auth_u |
|
293 | 293 | }) |
|
294 | 294 | |
|
295 | 295 | # register some common functions for usage |
|
296 | 296 | attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id) |
|
297 | 297 | |
|
298 | 298 | statsd = request.registry.statsd |
|
299 | 299 | |
|
300 | 300 | try: |
|
301 | 301 | ret_value = func(**call_params) |
|
302 | 302 | resp = jsonrpc_response(request, ret_value) |
|
303 | 303 | if statsd: |
|
304 | 304 | statsd.incr('rhodecode_api_call_success_total') |
|
305 | 305 | return resp |
|
306 | 306 | except JSONRPCBaseError: |
|
307 | 307 | raise |
|
308 | 308 | except Exception: |
|
309 | 309 | log.exception('Unhandled exception occurred on api call: %s', func) |
|
310 | 310 | exc_info = sys.exc_info() |
|
311 | 311 | exc_id, exc_type_name = store_exception( |
|
312 | 312 | id(exc_info), exc_info, prefix='rhodecode-api') |
|
313 | 313 | error_headers = { |
|
314 | 314 | 'RhodeCode-Exception-Id': str(exc_id), |
|
315 | 315 | 'RhodeCode-Exception-Type': str(exc_type_name) |
|
316 | 316 | } |
|
317 | 317 | err_resp = jsonrpc_error( |
|
318 | 318 | request, retid=request.rpc_id, message='Internal server error', |
|
319 | 319 | headers=error_headers) |
|
320 | 320 | if statsd: |
|
321 | 321 | statsd.incr('rhodecode_api_call_fail_total') |
|
322 | 322 | return err_resp |
|
323 | 323 | |
|
324 | 324 | |
|
325 | 325 | def setup_request(request): |
|
326 | 326 | """ |
|
327 | 327 | Parse a JSON-RPC request body. It's used inside the predicates method |
|
328 | 328 | to validate and bootstrap requests for usage in rpc calls. |
|
329 | 329 | |
|
330 | 330 | We need to raise JSONRPCError here if we want to return some errors back to |
|
331 | 331 | user. |
|
332 | 332 | """ |
|
333 | 333 | |
|
334 | 334 | log.debug('Executing setup request: %r', request) |
|
335 | 335 | request.rpc_ip_addr = get_ip_addr(request.environ) |
|
336 | 336 | # TODO(marcink): deprecate GET at some point |
|
337 | 337 | if request.method not in ['POST', 'GET']: |
|
338 | 338 | log.debug('unsupported request method "%s"', request.method) |
|
339 | 339 | raise JSONRPCError( |
|
340 | 340 | 'unsupported request method "%s". Please use POST' % request.method) |
|
341 | 341 | |
|
342 | 342 | if 'CONTENT_LENGTH' not in request.environ: |
|
343 | 343 | log.debug("No Content-Length") |
|
344 | 344 | raise JSONRPCError("Empty body, No Content-Length in request") |
|
345 | 345 | |
|
346 | 346 | else: |
|
347 | 347 | length = request.environ['CONTENT_LENGTH'] |
|
348 | 348 | log.debug('Content-Length: %s', length) |
|
349 | 349 | |
|
350 | 350 | if length == 0: |
|
351 | 351 | log.debug("Content-Length is 0") |
|
352 | 352 | raise JSONRPCError("Content-Length is 0") |
|
353 | 353 | |
|
354 | 354 | raw_body = request.body |
|
355 | 355 | log.debug("Loading JSON body now") |
|
356 | 356 | try: |
|
357 | 357 | json_body = ext_json.json.loads(raw_body) |
|
358 | 358 | except ValueError as e: |
|
359 | 359 | # catch JSON errors Here |
|
360 | 360 | raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body)) |
|
361 | 361 | |
|
362 | 362 | request.rpc_id = json_body.get('id') |
|
363 | 363 | request.rpc_method = json_body.get('method') |
|
364 | 364 | |
|
365 | 365 | # check required base parameters |
|
366 | 366 | try: |
|
367 | 367 | api_key = json_body.get('api_key') |
|
368 | 368 | if not api_key: |
|
369 | 369 | api_key = json_body.get('auth_token') |
|
370 | 370 | |
|
371 | 371 | if not api_key: |
|
372 | 372 | raise KeyError('api_key or auth_token') |
|
373 | 373 | |
|
374 | 374 | # TODO(marcink): support passing in token in request header |
|
375 | 375 | |
|
376 | 376 | request.rpc_api_key = api_key |
|
377 | 377 | request.rpc_id = json_body['id'] |
|
378 | 378 | request.rpc_method = json_body['method'] |
|
379 | 379 | request.rpc_params = json_body['args'] \ |
|
380 | 380 | if isinstance(json_body['args'], dict) else {} |
|
381 | 381 | |
|
382 | 382 | log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params) |
|
383 | 383 | except KeyError as e: |
|
384 | 384 | raise JSONRPCError(f'Incorrect JSON data. Missing {e}') |
|
385 | 385 | |
|
386 | 386 | log.debug('setup complete, now handling method:%s rpcid:%s', |
|
387 | 387 | request.rpc_method, request.rpc_id, ) |
|
388 | 388 | |
|
389 | 389 | |
|
390 | 390 | class RoutePredicate(object): |
|
391 | 391 | def __init__(self, val, config): |
|
392 | 392 | self.val = val |
|
393 | 393 | |
|
394 | 394 | def text(self): |
|
395 | 395 | return f'jsonrpc route = {self.val}' |
|
396 | 396 | |
|
397 | 397 | phash = text |
|
398 | 398 | |
|
399 | 399 | def __call__(self, info, request): |
|
400 | 400 | if self.val: |
|
401 | 401 | # potentially setup and bootstrap our call |
|
402 | 402 | setup_request(request) |
|
403 | 403 | |
|
404 | 404 | # Always return True so that even if it isn't a valid RPC it |
|
405 | 405 | # will fall through to the underlaying handlers like notfound_view |
|
406 | 406 | return True |
|
407 | 407 | |
|
408 | 408 | |
|
409 | 409 | class NotFoundPredicate(object): |
|
410 | 410 | def __init__(self, val, config): |
|
411 | 411 | self.val = val |
|
412 | 412 | self.methods = config.registry.jsonrpc_methods |
|
413 | 413 | |
|
414 | 414 | def text(self): |
|
415 | 415 | return f'jsonrpc method not found = {self.val}' |
|
416 | 416 | |
|
417 | 417 | phash = text |
|
418 | 418 | |
|
419 | 419 | def __call__(self, info, request): |
|
420 | 420 | return hasattr(request, 'rpc_method') |
|
421 | 421 | |
|
422 | 422 | |
|
423 | 423 | class MethodPredicate(object): |
|
424 | 424 | def __init__(self, val, config): |
|
425 | 425 | self.method = val |
|
426 | 426 | |
|
427 | 427 | def text(self): |
|
428 | 428 | return f'jsonrpc method = {self.method}' |
|
429 | 429 | |
|
430 | 430 | phash = text |
|
431 | 431 | |
|
432 | 432 | def __call__(self, context, request): |
|
433 | 433 | # we need to explicitly return False here, so pyramid doesn't try to |
|
434 | 434 | # execute our view directly. We need our main handler to execute things |
|
435 | 435 | return getattr(request, 'rpc_method') == self.method |
|
436 | 436 | |
|
437 | 437 | |
|
438 | 438 | def add_jsonrpc_method(config, view, **kwargs): |
|
439 | 439 | # pop the method name |
|
440 | 440 | method = kwargs.pop('method', None) |
|
441 | 441 | |
|
442 | 442 | if method is None: |
|
443 | 443 | raise ConfigurationError( |
|
444 | 444 | 'Cannot register a JSON-RPC method without specifying the "method"') |
|
445 | 445 | |
|
446 | 446 | # we define custom predicate, to enable to detect conflicting methods, |
|
447 | 447 | # those predicates are kind of "translation" from the decorator variables |
|
448 | 448 | # to internal predicates names |
|
449 | 449 | |
|
450 | 450 | kwargs['jsonrpc_method'] = method |
|
451 | 451 | |
|
452 | 452 | # register our view into global view store for validation |
|
453 | 453 | config.registry.jsonrpc_methods[method] = view |
|
454 | 454 | |
|
455 | 455 | # we're using our main request_view handler, here, so each method |
|
456 | 456 | # has a unified handler for itself |
|
457 | 457 | config.add_view(request_view, route_name='apiv2', **kwargs) |
|
458 | 458 | |
|
459 | 459 | |
|
460 | 460 | class jsonrpc_method(object): |
|
461 | 461 | """ |
|
462 | 462 | decorator that works similar to @add_view_config decorator, |
|
463 | 463 | but tailored for our JSON RPC |
|
464 | 464 | """ |
|
465 | 465 | |
|
466 | 466 | venusian = venusian # for testing injection |
|
467 | 467 | |
|
468 | 468 | def __init__(self, method=None, **kwargs): |
|
469 | 469 | self.method = method |
|
470 | 470 | self.kwargs = kwargs |
|
471 | 471 | |
|
472 | 472 | def __call__(self, wrapped): |
|
473 | 473 | kwargs = self.kwargs.copy() |
|
474 | 474 | kwargs['method'] = self.method or wrapped.__name__ |
|
475 | 475 | depth = kwargs.pop('_depth', 0) |
|
476 | 476 | |
|
477 | 477 | def callback(context, name, ob): |
|
478 | 478 | config = context.config.with_package(info.module) |
|
479 | 479 | config.add_jsonrpc_method(view=ob, **kwargs) |
|
480 | 480 | |
|
481 | 481 | info = venusian.attach(wrapped, callback, category='pyramid', |
|
482 | 482 | depth=depth + 1) |
|
483 | 483 | if info.scope == 'class': |
|
484 | 484 | # ensure that attr is set if decorating a class method |
|
485 | 485 | kwargs.setdefault('attr', wrapped.__name__) |
|
486 | 486 | |
|
487 | 487 | kwargs['_info'] = info.codeinfo # fbo action_method |
|
488 | 488 | return wrapped |
|
489 | 489 | |
|
490 | 490 | |
|
491 | 491 | class jsonrpc_deprecated_method(object): |
|
492 | 492 | """ |
|
493 | 493 | Marks method as deprecated, adds log.warning, and inject special key to |
|
494 | 494 | the request variable to mark method as deprecated. |
|
495 | 495 | Also injects special docstring that extract_docs will catch to mark |
|
496 | 496 | method as deprecated. |
|
497 | 497 | |
|
498 | 498 | :param use_method: specify which method should be used instead of |
|
499 | 499 | the decorated one |
|
500 | 500 | |
|
501 | 501 | Use like:: |
|
502 | 502 | |
|
503 | 503 | @jsonrpc_method() |
|
504 | 504 | @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0') |
|
505 | 505 | def old_func(request, apiuser, arg1, arg2): |
|
506 | 506 | ... |
|
507 | 507 | """ |
|
508 | 508 | |
|
509 | 509 | def __init__(self, use_method, deprecated_at_version): |
|
510 | 510 | self.use_method = use_method |
|
511 | 511 | self.deprecated_at_version = deprecated_at_version |
|
512 | 512 | self.deprecated_msg = '' |
|
513 | 513 | |
|
514 | 514 | def __call__(self, func): |
|
515 | 515 | self.deprecated_msg = 'Please use method `{method}` instead.'.format( |
|
516 | 516 | method=self.use_method) |
|
517 | 517 | |
|
518 | 518 | docstring = """\n |
|
519 | 519 | .. deprecated:: {version} |
|
520 | 520 | |
|
521 | 521 | {deprecation_message} |
|
522 | 522 | |
|
523 | 523 | {original_docstring} |
|
524 | 524 | """ |
|
525 | 525 | func.__doc__ = docstring.format( |
|
526 | 526 | version=self.deprecated_at_version, |
|
527 | 527 | deprecation_message=self.deprecated_msg, |
|
528 | 528 | original_docstring=func.__doc__) |
|
529 | 529 | return decorator.decorator(self.__wrapper, func) |
|
530 | 530 | |
|
531 | 531 | def __wrapper(self, func, *fargs, **fkwargs): |
|
532 | 532 | log.warning('DEPRECATED API CALL on function %s, please ' |
|
533 | 533 | 'use `%s` instead', func, self.use_method) |
|
534 | 534 | # alter function docstring to mark as deprecated, this is picked up |
|
535 | 535 | # via fabric file that generates API DOC. |
|
536 | 536 | result = func(*fargs, **fkwargs) |
|
537 | 537 | |
|
538 | 538 | request = fargs[0] |
|
539 | 539 | request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg |
|
540 | 540 | return result |
|
541 | 541 | |
|
542 | 542 | |
|
543 | 543 | def add_api_methods(config): |
|
544 | 544 | from rhodecode.api.views import ( |
|
545 | 545 | deprecated_api, gist_api, pull_request_api, repo_api, repo_group_api, |
|
546 | 546 | server_api, search_api, testing_api, user_api, user_group_api) |
|
547 | 547 | |
|
548 | 548 | config.scan('rhodecode.api.views') |
|
549 | 549 | |
|
550 | 550 | |
|
551 | 551 | def includeme(config): |
|
552 | 552 | plugin_module = 'rhodecode.api' |
|
553 | 553 | plugin_settings = get_plugin_settings( |
|
554 | 554 | plugin_module, config.registry.settings) |
|
555 | 555 | |
|
556 | 556 | if not hasattr(config.registry, 'jsonrpc_methods'): |
|
557 | 557 | config.registry.jsonrpc_methods = OrderedDict() |
|
558 | 558 | |
|
559 | 559 | # match filter by given method only |
|
560 | 560 | config.add_view_predicate('jsonrpc_method', MethodPredicate) |
|
561 | 561 | config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate) |
|
562 | 562 | |
|
563 | 563 | config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer()) |
|
564 | 564 | config.add_directive('add_jsonrpc_method', add_jsonrpc_method) |
|
565 | 565 | |
|
566 | 566 | config.add_route_predicate( |
|
567 | 567 | 'jsonrpc_call', RoutePredicate) |
|
568 | 568 | |
|
569 | 569 | config.add_route( |
|
570 | 570 | 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True) |
|
571 | 571 | |
|
572 | 572 | # register some exception handling view |
|
573 | 573 | config.add_view(exception_view, context=JSONRPCBaseError) |
|
574 | 574 | config.add_notfound_view(exception_view, jsonrpc_method_not_found=True) |
|
575 | 575 | |
|
576 | 576 | add_api_methods(config) |
@@ -1,42 +1,42 b'' | |||
|
1 | 1 | |
|
2 | 2 | |
|
3 |
# Copyright (C) 2011-202 |
|
|
3 | # Copyright (C) 2011-2023 RhodeCode GmbH | |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | class JSONRPCBaseError(Exception): |
|
23 | 23 | def __init__(self, message='', *args): |
|
24 | 24 | self.message = message |
|
25 | 25 | super(JSONRPCBaseError, self).__init__(message, *args) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | class JSONRPCError(JSONRPCBaseError): |
|
29 | 29 | pass |
|
30 | 30 | |
|
31 | 31 | |
|
32 | 32 | class JSONRPCValidationError(JSONRPCBaseError): |
|
33 | 33 | |
|
34 | 34 | def __init__(self, *args, **kwargs): |
|
35 | 35 | self.colander_exception = kwargs.pop('colander_exc') |
|
36 | 36 | super(JSONRPCValidationError, self).__init__( |
|
37 | 37 | message=self.colander_exception, *args) |
|
38 | 38 | |
|
39 | 39 | |
|
40 | 40 | class JSONRPCForbidden(JSONRPCBaseError): |
|
41 | 41 | pass |
|
42 | 42 |
@@ -1,18 +1,18 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
@@ -1,51 +1,51 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.meta import Session |
|
23 | 23 | from rhodecode.model.user import UserModel |
|
24 | 24 | from rhodecode.model.auth_token import AuthTokenModel |
|
25 | 25 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.fixture(scope="class") |
|
29 | 29 | def testuser_api(request, baseapp): |
|
30 | 30 | cls = request.cls |
|
31 | 31 | |
|
32 | 32 | # ADMIN USER |
|
33 | 33 | cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
34 | 34 | cls.apikey = cls.usr.api_key |
|
35 | 35 | |
|
36 | 36 | # REGULAR USER |
|
37 | 37 | cls.test_user = UserModel().create_or_update( |
|
38 | 38 | username='test-api', |
|
39 | 39 | password='test', |
|
40 | 40 | email='test@api.rhodecode.org', |
|
41 | 41 | firstname='first', |
|
42 | 42 | lastname='last' |
|
43 | 43 | ) |
|
44 | 44 | # create TOKEN for user, if he doesn't have one |
|
45 | 45 | if not cls.test_user.api_key: |
|
46 | 46 | AuthTokenModel().create( |
|
47 | 47 | user=cls.test_user, description=u'TEST_USER_TOKEN') |
|
48 | 48 | |
|
49 | 49 | Session().commit() |
|
50 | 50 | cls.TEST_USER_LOGIN = cls.test_user.username |
|
51 | 51 | cls.apikey_regular = cls.test_user.api_key |
@@ -1,61 +1,61 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.db import Repository, RepositoryField |
|
23 | 23 | from rhodecode.api.tests.utils import ( |
|
24 | 24 | build_data, api_call, assert_ok, assert_error) |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | @pytest.mark.usefixtures("testuser_api", "app") |
|
28 | 28 | class TestAddFieldToRepo(object): |
|
29 | 29 | def test_api_add_field_to_repo(self, backend): |
|
30 | 30 | repo = backend.create_repo() |
|
31 | 31 | repo_name = repo.repo_name |
|
32 | 32 | id_, params = build_data( |
|
33 | 33 | self.apikey, 'add_field_to_repo', |
|
34 | 34 | repoid=repo_name, |
|
35 | 35 | key='extra_field', |
|
36 | 36 | label='extra_field_label', |
|
37 | 37 | description='extra_field_desc') |
|
38 | 38 | response = api_call(self.app, params) |
|
39 | 39 | expected = { |
|
40 | 40 | 'msg': 'Added new repository field `extra_field`', |
|
41 | 41 | 'success': True, |
|
42 | 42 | } |
|
43 | 43 | assert_ok(id_, expected, given=response.body) |
|
44 | 44 | |
|
45 | 45 | repo = Repository.get_by_repo_name(repo_name) |
|
46 | 46 | repo_field = RepositoryField.get_by_key_name('extra_field', repo) |
|
47 | 47 | _data = repo_field.get_dict() |
|
48 | 48 | assert _data['field_desc'] == 'extra_field_desc' |
|
49 | 49 | assert _data['field_key'] == 'extra_field' |
|
50 | 50 | assert _data['field_label'] == 'extra_field_label' |
|
51 | 51 | |
|
52 | 52 | id_, params = build_data( |
|
53 | 53 | self.apikey, 'add_field_to_repo', |
|
54 | 54 | repoid=repo_name, |
|
55 | 55 | key='extra_field', |
|
56 | 56 | label='extra_field_label', |
|
57 | 57 | description='extra_field_desc') |
|
58 | 58 | response = api_call(self.app, params) |
|
59 | 59 | expected = 'Field with key `extra_field` exists for repo `%s`' % ( |
|
60 | 60 | repo_name) |
|
61 | 61 | assert_error(id_, expected, given=response.body) |
@@ -1,71 +1,71 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.user_group import UserGroupModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestAddUserToUserGroup(object): |
|
30 | 30 | def test_api_add_user_to_user_group(self, user_util): |
|
31 | 31 | group = user_util.create_user_group() |
|
32 | 32 | user = user_util.create_user() |
|
33 | 33 | group_name = group.users_group_name |
|
34 | 34 | user_name = user.username |
|
35 | 35 | id_, params = build_data( |
|
36 | 36 | self.apikey, 'add_user_to_user_group', |
|
37 | 37 | usergroupid=group_name, userid=user_name) |
|
38 | 38 | response = api_call(self.app, params) |
|
39 | 39 | expected = { |
|
40 | 40 | 'msg': 'added member `%s` to user group `%s`' % ( |
|
41 | 41 | user_name, group_name |
|
42 | 42 | ), |
|
43 | 43 | 'success': True |
|
44 | 44 | } |
|
45 | 45 | assert_ok(id_, expected, given=response.body) |
|
46 | 46 | |
|
47 | 47 | def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util): |
|
48 | 48 | user = user_util.create_user() |
|
49 | 49 | user_name = user.username |
|
50 | 50 | id_, params = build_data( |
|
51 | 51 | self.apikey, 'add_user_to_user_group', |
|
52 | 52 | usergroupid='false-group', |
|
53 | 53 | userid=user_name) |
|
54 | 54 | response = api_call(self.app, params) |
|
55 | 55 | |
|
56 | 56 | expected = 'user group `%s` does not exist' % 'false-group' |
|
57 | 57 | assert_error(id_, expected, given=response.body) |
|
58 | 58 | |
|
59 | 59 | @mock.patch.object(UserGroupModel, 'add_user_to_group', crash) |
|
60 | 60 | def test_api_add_user_to_user_group_exception_occurred(self, user_util): |
|
61 | 61 | group = user_util.create_user_group() |
|
62 | 62 | user = user_util.create_user() |
|
63 | 63 | group_name = group.users_group_name |
|
64 | 64 | user_name = user.username |
|
65 | 65 | id_, params = build_data( |
|
66 | 66 | self.apikey, 'add_user_to_user_group', |
|
67 | 67 | usergroupid=group_name, userid=user_name) |
|
68 | 68 | response = api_call(self.app, params) |
|
69 | 69 | |
|
70 | 70 | expected = 'failed to add member to user group `%s`' % (group_name,) |
|
71 | 71 | assert_error(id_, expected, given=response.body) |
@@ -1,133 +1,133 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.api.utils import Optional, OAttr |
|
23 | 23 | from rhodecode.api.tests.utils import ( |
|
24 | 24 | build_data, api_call, assert_error, assert_ok) |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | @pytest.mark.usefixtures("testuser_api", "app") |
|
28 | 28 | class TestApi(object): |
|
29 | 29 | maxDiff = None |
|
30 | 30 | |
|
31 | 31 | def test_Optional_object(self): |
|
32 | 32 | |
|
33 | 33 | option1 = Optional(None) |
|
34 | 34 | assert '<Optional:%s>' % (None,) == repr(option1) |
|
35 | 35 | assert option1() is None |
|
36 | 36 | |
|
37 | 37 | assert 1 == Optional.extract(Optional(1)) |
|
38 | 38 | assert 'example' == Optional.extract('example') |
|
39 | 39 | |
|
40 | 40 | def test_Optional_OAttr(self): |
|
41 | 41 | option1 = Optional(OAttr('apiuser')) |
|
42 | 42 | assert 'apiuser' == Optional.extract(option1) |
|
43 | 43 | |
|
44 | 44 | def test_OAttr_object(self): |
|
45 | 45 | oattr1 = OAttr('apiuser') |
|
46 | 46 | assert '<OptionalAttr:apiuser>' == repr(oattr1) |
|
47 | 47 | assert oattr1() == oattr1 |
|
48 | 48 | |
|
49 | 49 | def test_api_wrong_key(self): |
|
50 | 50 | id_, params = build_data('trololo', 'get_user') |
|
51 | 51 | response = api_call(self.app, params) |
|
52 | 52 | |
|
53 | 53 | expected = 'Invalid API KEY' |
|
54 | 54 | assert_error(id_, expected, given=response.body) |
|
55 | 55 | |
|
56 | 56 | def test_api_missing_non_optional_param(self): |
|
57 | 57 | id_, params = build_data(self.apikey, 'get_repo') |
|
58 | 58 | response = api_call(self.app, params) |
|
59 | 59 | |
|
60 | 60 | expected = 'Missing non optional `repoid` arg in JSON DATA' |
|
61 | 61 | assert_error(id_, expected, given=response.body) |
|
62 | 62 | |
|
63 | 63 | def test_api_missing_non_optional_param_args_null(self): |
|
64 | 64 | id_, params = build_data(self.apikey, 'get_repo') |
|
65 | 65 | params = params.replace(b'"args": {}', b'"args": null') |
|
66 | 66 | response = api_call(self.app, params) |
|
67 | 67 | |
|
68 | 68 | expected = 'Missing non optional `repoid` arg in JSON DATA' |
|
69 | 69 | assert_error(id_, expected, given=response.body) |
|
70 | 70 | |
|
71 | 71 | def test_api_missing_non_optional_param_args_bad(self): |
|
72 | 72 | id_, params = build_data(self.apikey, 'get_repo') |
|
73 | 73 | params = params.replace(b'"args": {}', b'"args": 1') |
|
74 | 74 | response = api_call(self.app, params) |
|
75 | 75 | |
|
76 | 76 | expected = 'Missing non optional `repoid` arg in JSON DATA' |
|
77 | 77 | assert_error(id_, expected, given=response.body) |
|
78 | 78 | |
|
79 | 79 | def test_api_non_existing_method(self, request): |
|
80 | 80 | id_, params = build_data(self.apikey, 'not_existing', args='xx') |
|
81 | 81 | response = api_call(self.app, params) |
|
82 | 82 | expected = 'No such method: not_existing. Similar methods: none' |
|
83 | 83 | assert_error(id_, expected, given=response.body) |
|
84 | 84 | |
|
85 | 85 | def test_api_non_existing_method_have_similar(self, request): |
|
86 | 86 | id_, params = build_data(self.apikey, 'comment', args='xx') |
|
87 | 87 | response = api_call(self.app, params) |
|
88 | 88 | expected = 'No such method: comment. ' \ |
|
89 | 89 | 'Similar methods: changeset_comment, comment_pull_request, ' \ |
|
90 | 90 | 'get_pull_request_comments, comment_commit, edit_comment, ' \ |
|
91 | 91 | 'get_comment, get_repo_comments' |
|
92 | 92 | assert_error(id_, expected, given=response.body) |
|
93 | 93 | |
|
94 | 94 | def test_api_disabled_user(self, request): |
|
95 | 95 | |
|
96 | 96 | def set_active(active): |
|
97 | 97 | from rhodecode.model.db import Session, User |
|
98 | 98 | user = User.get_by_auth_token(self.apikey) |
|
99 | 99 | user.active = active |
|
100 | 100 | Session().add(user) |
|
101 | 101 | Session().commit() |
|
102 | 102 | |
|
103 | 103 | request.addfinalizer(lambda: set_active(True)) |
|
104 | 104 | |
|
105 | 105 | set_active(False) |
|
106 | 106 | id_, params = build_data(self.apikey, 'test', args='xx') |
|
107 | 107 | response = api_call(self.app, params) |
|
108 | 108 | expected = 'Request from this user not allowed' |
|
109 | 109 | assert_error(id_, expected, given=response.body) |
|
110 | 110 | |
|
111 | 111 | def test_api_args_is_null(self): |
|
112 | 112 | __, params = build_data(self.apikey, 'get_users', ) |
|
113 | 113 | params = params.replace(b'"args": {}', b'"args": null') |
|
114 | 114 | response = api_call(self.app, params) |
|
115 | 115 | assert response.status == '200 OK' |
|
116 | 116 | |
|
117 | 117 | def test_api_args_is_bad(self): |
|
118 | 118 | __, params = build_data(self.apikey, 'get_users', ) |
|
119 | 119 | params = params.replace(b'"args": {}', b'"args": 1') |
|
120 | 120 | response = api_call(self.app, params) |
|
121 | 121 | assert response.status == '200 OK' |
|
122 | 122 | |
|
123 | 123 | def test_api_args_different_args(self): |
|
124 | 124 | import string |
|
125 | 125 | expected = { |
|
126 | 126 | 'ascii_letters': string.ascii_letters, |
|
127 | 127 | 'ws': string.whitespace, |
|
128 | 128 | 'printables': string.printable |
|
129 | 129 | } |
|
130 | 130 | id_, params = build_data(self.apikey, 'test', args=expected) |
|
131 | 131 | response = api_call(self.app, params) |
|
132 | 132 | assert response.status == '200 OK' |
|
133 | 133 | assert_ok(id_, expected, response.body) |
@@ -1,44 +1,44 b'' | |||
|
1 | 1 | |
|
2 | 2 | |
|
3 |
# Copyright (C) 2017-202 |
|
|
3 | # Copyright (C) 2017-2023 RhodeCode GmbH | |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | import mock |
|
22 | 22 | import pytest |
|
23 | 23 | |
|
24 | 24 | from rhodecode.lib.user_sessions import FileAuthSessions |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_ok, assert_error, crash) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestCleanupSessions(object): |
|
31 | 31 | def test_api_cleanup_sessions(self): |
|
32 | 32 | id_, params = build_data(self.apikey, 'cleanup_sessions') |
|
33 | 33 | response = api_call(self.app, params) |
|
34 | 34 | |
|
35 | 35 | expected = {'backend': 'file sessions', 'sessions_removed': 0} |
|
36 | 36 | assert_ok(id_, expected, given=response.body) |
|
37 | 37 | |
|
38 | 38 | @mock.patch.object(FileAuthSessions, 'clean_sessions', crash) |
|
39 | 39 | def test_api_cleanup_error(self): |
|
40 | 40 | id_, params = build_data(self.apikey, 'cleanup_sessions', ) |
|
41 | 41 | response = api_call(self.app, params) |
|
42 | 42 | |
|
43 | 43 | expected = 'Error occurred during session cleanup' |
|
44 | 44 | assert_error(id_, expected, given=response.body) |
@@ -1,112 +1,112 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.db import UserLog |
|
23 | 23 | from rhodecode.model.pull_request import PullRequestModel |
|
24 | 24 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestClosePullRequest(object): |
|
31 | 31 | |
|
32 | 32 | @pytest.mark.backends("git", "hg") |
|
33 | 33 | def test_api_close_pull_request(self, pr_util): |
|
34 | 34 | pull_request = pr_util.create_pull_request() |
|
35 | 35 | pull_request_id = pull_request.pull_request_id |
|
36 | 36 | author = pull_request.user_id |
|
37 | 37 | repo = pull_request.target_repo.repo_id |
|
38 | 38 | id_, params = build_data( |
|
39 | 39 | self.apikey, 'close_pull_request', |
|
40 | 40 | repoid=pull_request.target_repo.repo_name, |
|
41 | 41 | pullrequestid=pull_request.pull_request_id) |
|
42 | 42 | response = api_call(self.app, params) |
|
43 | 43 | expected = { |
|
44 | 44 | 'pull_request_id': pull_request_id, |
|
45 | 45 | 'close_status': 'Rejected', |
|
46 | 46 | 'closed': True, |
|
47 | 47 | } |
|
48 | 48 | assert_ok(id_, expected, response.body) |
|
49 | 49 | journal = UserLog.query()\ |
|
50 | 50 | .filter(UserLog.user_id == author) \ |
|
51 | 51 | .order_by(UserLog.user_log_id.asc()) \ |
|
52 | 52 | .filter(UserLog.repository_id == repo)\ |
|
53 | 53 | .all() |
|
54 | 54 | assert journal[-1].action == 'repo.pull_request.close' |
|
55 | 55 | |
|
56 | 56 | @pytest.mark.backends("git", "hg") |
|
57 | 57 | def test_api_close_pull_request_already_closed_error(self, pr_util): |
|
58 | 58 | pull_request = pr_util.create_pull_request() |
|
59 | 59 | pull_request_id = pull_request.pull_request_id |
|
60 | 60 | pull_request_repo = pull_request.target_repo.repo_name |
|
61 | 61 | PullRequestModel().close_pull_request( |
|
62 | 62 | pull_request, pull_request.author) |
|
63 | 63 | id_, params = build_data( |
|
64 | 64 | self.apikey, 'close_pull_request', |
|
65 | 65 | repoid=pull_request_repo, pullrequestid=pull_request_id) |
|
66 | 66 | response = api_call(self.app, params) |
|
67 | 67 | |
|
68 | 68 | expected = 'pull request `%s` is already closed' % pull_request_id |
|
69 | 69 | assert_error(id_, expected, given=response.body) |
|
70 | 70 | |
|
71 | 71 | @pytest.mark.backends("git", "hg") |
|
72 | 72 | def test_api_close_pull_request_repo_error(self, pr_util): |
|
73 | 73 | pull_request = pr_util.create_pull_request() |
|
74 | 74 | id_, params = build_data( |
|
75 | 75 | self.apikey, 'close_pull_request', |
|
76 | 76 | repoid=666, pullrequestid=pull_request.pull_request_id) |
|
77 | 77 | response = api_call(self.app, params) |
|
78 | 78 | |
|
79 | 79 | expected = 'repository `666` does not exist' |
|
80 | 80 | assert_error(id_, expected, given=response.body) |
|
81 | 81 | |
|
82 | 82 | @pytest.mark.backends("git", "hg") |
|
83 | 83 | def test_api_close_pull_request_non_admin_with_userid_error(self, |
|
84 | 84 | pr_util): |
|
85 | 85 | pull_request = pr_util.create_pull_request() |
|
86 | 86 | id_, params = build_data( |
|
87 | 87 | self.apikey_regular, 'close_pull_request', |
|
88 | 88 | repoid=pull_request.target_repo.repo_name, |
|
89 | 89 | pullrequestid=pull_request.pull_request_id, |
|
90 | 90 | userid=TEST_USER_ADMIN_LOGIN) |
|
91 | 91 | response = api_call(self.app, params) |
|
92 | 92 | |
|
93 | 93 | expected = 'userid is not the same as your user' |
|
94 | 94 | assert_error(id_, expected, given=response.body) |
|
95 | 95 | |
|
96 | 96 | @pytest.mark.backends("git", "hg") |
|
97 | 97 | def test_api_close_pull_request_no_perms_to_close( |
|
98 | 98 | self, user_util, pr_util): |
|
99 | 99 | user = user_util.create_user() |
|
100 | 100 | pull_request = pr_util.create_pull_request() |
|
101 | 101 | |
|
102 | 102 | id_, params = build_data( |
|
103 | 103 | user.api_key, 'close_pull_request', |
|
104 | 104 | repoid=pull_request.target_repo.repo_name, |
|
105 | 105 | pullrequestid=pull_request.pull_request_id,) |
|
106 | 106 | response = api_call(self.app, params) |
|
107 | 107 | |
|
108 | 108 | expected = ('pull request `%s` close failed, ' |
|
109 | 109 | 'no permission to close.') % pull_request.pull_request_id |
|
110 | 110 | |
|
111 | 111 | response_json = response.json['error'] |
|
112 | 112 | assert response_json == expected |
@@ -1,115 +1,115 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.db import ChangesetStatus, User |
|
23 | 23 | from rhodecode.api.tests.utils import ( |
|
24 | 24 | build_data, api_call, assert_error, assert_ok) |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | @pytest.mark.usefixtures("testuser_api", "app") |
|
28 | 28 | class TestCommentCommit(object): |
|
29 | 29 | def test_api_comment_commit_on_empty_repo(self, backend): |
|
30 | 30 | repo = backend.create_repo() |
|
31 | 31 | id_, params = build_data( |
|
32 | 32 | self.apikey, 'comment_commit', repoid=repo.repo_name, |
|
33 | 33 | commit_id='tip', message='message', status_change=None) |
|
34 | 34 | response = api_call(self.app, params) |
|
35 | 35 | expected = 'There are no commits yet' |
|
36 | 36 | assert_error(id_, expected, given=response.body) |
|
37 | 37 | |
|
38 | 38 | @pytest.mark.parametrize("commit_id, expected_err", [ |
|
39 | 39 | ('abcabca', {'hg': 'Commit {commit} does not exist for `{repo}`', |
|
40 | 40 | 'git': 'Commit {commit} does not exist for `{repo}`', |
|
41 | 41 | 'svn': 'Commit id {commit} not understood.'}), |
|
42 | 42 | ('idontexist', {'hg': 'Commit {commit} does not exist for `{repo}`', |
|
43 | 43 | 'git': 'Commit {commit} does not exist for `{repo}`', |
|
44 | 44 | 'svn': 'Commit id {commit} not understood.'}), |
|
45 | 45 | ]) |
|
46 | 46 | def test_api_comment_commit_wrong_hash(self, backend, commit_id, expected_err): |
|
47 | 47 | repo_name = backend.repo.repo_name |
|
48 | 48 | id_, params = build_data( |
|
49 | 49 | self.apikey, 'comment_commit', repoid=repo_name, |
|
50 | 50 | commit_id=commit_id, message='message', status_change=None) |
|
51 | 51 | response = api_call(self.app, params) |
|
52 | 52 | |
|
53 | 53 | expected_err = expected_err[backend.alias] |
|
54 | 54 | expected_err = expected_err.format( |
|
55 | 55 | repo=backend.repo.scm_instance().name, commit=commit_id) |
|
56 | 56 | assert_error(id_, expected_err, given=response.body) |
|
57 | 57 | |
|
58 | 58 | @pytest.mark.parametrize("status_change, message, commit_id", [ |
|
59 | 59 | (None, 'Hallo', 'tip'), |
|
60 | 60 | (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'), |
|
61 | 61 | (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'), |
|
62 | 62 | ]) |
|
63 | 63 | def test_api_comment_commit( |
|
64 | 64 | self, backend, status_change, message, commit_id, |
|
65 | 65 | no_notifications): |
|
66 | 66 | |
|
67 | 67 | commit_id = backend.repo.scm_instance().get_commit(commit_id).raw_id |
|
68 | 68 | |
|
69 | 69 | id_, params = build_data( |
|
70 | 70 | self.apikey, 'comment_commit', repoid=backend.repo_name, |
|
71 | 71 | commit_id=commit_id, message=message, status=status_change) |
|
72 | 72 | response = api_call(self.app, params) |
|
73 | 73 | repo = backend.repo.scm_instance() |
|
74 | 74 | expected = { |
|
75 | 75 | 'msg': 'Commented on commit `%s` for repository `%s`' % ( |
|
76 | 76 | repo.get_commit().raw_id, backend.repo_name), |
|
77 | 77 | 'status_change': status_change, |
|
78 | 78 | 'success': True |
|
79 | 79 | } |
|
80 | 80 | assert_ok(id_, expected, given=response.body) |
|
81 | 81 | |
|
82 | 82 | def test_api_comment_commit_with_extra_recipients(self, backend, user_util): |
|
83 | 83 | |
|
84 | 84 | commit_id = backend.repo.scm_instance().get_commit('tip').raw_id |
|
85 | 85 | |
|
86 | 86 | user1 = user_util.create_user() |
|
87 | 87 | user1_id = user1.user_id |
|
88 | 88 | user2 = user_util.create_user() |
|
89 | 89 | user2_id = user2.user_id |
|
90 | 90 | |
|
91 | 91 | id_, params = build_data( |
|
92 | 92 | self.apikey, 'comment_commit', repoid=backend.repo_name, |
|
93 | 93 | commit_id=commit_id, |
|
94 | 94 | message='abracadabra', |
|
95 | 95 | extra_recipients=[user1.user_id, user2.username]) |
|
96 | 96 | |
|
97 | 97 | response = api_call(self.app, params) |
|
98 | 98 | repo = backend.repo.scm_instance() |
|
99 | 99 | |
|
100 | 100 | expected = { |
|
101 | 101 | 'msg': 'Commented on commit `%s` for repository `%s`' % ( |
|
102 | 102 | repo.get_commit().raw_id, backend.repo_name), |
|
103 | 103 | 'status_change': None, |
|
104 | 104 | 'success': True |
|
105 | 105 | } |
|
106 | 106 | |
|
107 | 107 | assert_ok(id_, expected, given=response.body) |
|
108 | 108 | # check user1/user2 inbox for notification |
|
109 | 109 | user1 = User.get(user1_id) |
|
110 | 110 | assert 1 == len(user1.notifications) |
|
111 | 111 | assert 'abracadabra' in user1.notifications[0].notification.body |
|
112 | 112 | |
|
113 | 113 | user2 = User.get(user2_id) |
|
114 | 114 | assert 1 == len(user2.notifications) |
|
115 | 115 | assert 'abracadabra' in user2.notifications[0].notification.body |
@@ -1,380 +1,380 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.comment import CommentsModel |
|
23 | 23 | from rhodecode.model.db import UserLog, User, ChangesetComment |
|
24 | 24 | from rhodecode.model.pull_request import PullRequestModel |
|
25 | 25 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
26 | 26 | from rhodecode.api.tests.utils import ( |
|
27 | 27 | build_data, api_call, assert_error, assert_ok) |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
31 | 31 | class TestCommentPullRequest(object): |
|
32 | 32 | finalizers = [] |
|
33 | 33 | |
|
34 | 34 | def teardown_method(self, method): |
|
35 | 35 | if self.finalizers: |
|
36 | 36 | for finalizer in self.finalizers: |
|
37 | 37 | finalizer() |
|
38 | 38 | self.finalizers = [] |
|
39 | 39 | |
|
40 | 40 | @pytest.mark.backends("git", "hg") |
|
41 | 41 | def test_api_comment_pull_request(self, pr_util, no_notifications): |
|
42 | 42 | pull_request = pr_util.create_pull_request() |
|
43 | 43 | pull_request_id = pull_request.pull_request_id |
|
44 | 44 | author = pull_request.user_id |
|
45 | 45 | repo = pull_request.target_repo.repo_id |
|
46 | 46 | id_, params = build_data( |
|
47 | 47 | self.apikey, 'comment_pull_request', |
|
48 | 48 | repoid=pull_request.target_repo.repo_name, |
|
49 | 49 | pullrequestid=pull_request.pull_request_id, |
|
50 | 50 | message='test message') |
|
51 | 51 | response = api_call(self.app, params) |
|
52 | 52 | pull_request = PullRequestModel().get(pull_request.pull_request_id) |
|
53 | 53 | |
|
54 | 54 | comments = CommentsModel().get_comments( |
|
55 | 55 | pull_request.target_repo.repo_id, pull_request=pull_request) |
|
56 | 56 | |
|
57 | 57 | expected = { |
|
58 | 58 | 'pull_request_id': pull_request.pull_request_id, |
|
59 | 59 | 'comment_id': comments[-1].comment_id, |
|
60 | 60 | 'status': {'given': None, 'was_changed': None} |
|
61 | 61 | } |
|
62 | 62 | assert_ok(id_, expected, response.body) |
|
63 | 63 | |
|
64 | 64 | journal = UserLog.query()\ |
|
65 | 65 | .filter(UserLog.user_id == author)\ |
|
66 | 66 | .filter(UserLog.repository_id == repo) \ |
|
67 | 67 | .order_by(UserLog.user_log_id.asc()) \ |
|
68 | 68 | .all() |
|
69 | 69 | assert journal[-1].action == 'repo.pull_request.comment.create' |
|
70 | 70 | |
|
71 | 71 | @pytest.mark.backends("git", "hg") |
|
72 | 72 | def test_api_comment_pull_request_with_extra_recipients(self, pr_util, user_util): |
|
73 | 73 | pull_request = pr_util.create_pull_request() |
|
74 | 74 | |
|
75 | 75 | user1 = user_util.create_user() |
|
76 | 76 | user1_id = user1.user_id |
|
77 | 77 | user2 = user_util.create_user() |
|
78 | 78 | user2_id = user2.user_id |
|
79 | 79 | |
|
80 | 80 | id_, params = build_data( |
|
81 | 81 | self.apikey, 'comment_pull_request', |
|
82 | 82 | repoid=pull_request.target_repo.repo_name, |
|
83 | 83 | pullrequestid=pull_request.pull_request_id, |
|
84 | 84 | message='test message', |
|
85 | 85 | extra_recipients=[user1.user_id, user2.username] |
|
86 | 86 | ) |
|
87 | 87 | response = api_call(self.app, params) |
|
88 | 88 | pull_request = PullRequestModel().get(pull_request.pull_request_id) |
|
89 | 89 | |
|
90 | 90 | comments = CommentsModel().get_comments( |
|
91 | 91 | pull_request.target_repo.repo_id, pull_request=pull_request) |
|
92 | 92 | |
|
93 | 93 | expected = { |
|
94 | 94 | 'pull_request_id': pull_request.pull_request_id, |
|
95 | 95 | 'comment_id': comments[-1].comment_id, |
|
96 | 96 | 'status': {'given': None, 'was_changed': None} |
|
97 | 97 | } |
|
98 | 98 | assert_ok(id_, expected, response.body) |
|
99 | 99 | # check user1/user2 inbox for notification |
|
100 | 100 | user1 = User.get(user1_id) |
|
101 | 101 | assert 1 == len(user1.notifications) |
|
102 | 102 | assert 'test message' in user1.notifications[0].notification.body |
|
103 | 103 | |
|
104 | 104 | user2 = User.get(user2_id) |
|
105 | 105 | assert 1 == len(user2.notifications) |
|
106 | 106 | assert 'test message' in user2.notifications[0].notification.body |
|
107 | 107 | |
|
108 | 108 | @pytest.mark.backends("git", "hg") |
|
109 | 109 | def test_api_comment_pull_request_change_status( |
|
110 | 110 | self, pr_util, no_notifications): |
|
111 | 111 | pull_request = pr_util.create_pull_request() |
|
112 | 112 | pull_request_id = pull_request.pull_request_id |
|
113 | 113 | id_, params = build_data( |
|
114 | 114 | self.apikey, 'comment_pull_request', |
|
115 | 115 | repoid=pull_request.target_repo.repo_name, |
|
116 | 116 | pullrequestid=pull_request.pull_request_id, |
|
117 | 117 | status='rejected') |
|
118 | 118 | response = api_call(self.app, params) |
|
119 | 119 | pull_request = PullRequestModel().get(pull_request_id) |
|
120 | 120 | |
|
121 | 121 | comments = CommentsModel().get_comments( |
|
122 | 122 | pull_request.target_repo.repo_id, pull_request=pull_request) |
|
123 | 123 | expected = { |
|
124 | 124 | 'pull_request_id': pull_request.pull_request_id, |
|
125 | 125 | 'comment_id': comments[-1].comment_id, |
|
126 | 126 | 'status': {'given': 'rejected', 'was_changed': True} |
|
127 | 127 | } |
|
128 | 128 | assert_ok(id_, expected, response.body) |
|
129 | 129 | |
|
130 | 130 | @pytest.mark.backends("git", "hg") |
|
131 | 131 | def test_api_comment_pull_request_change_status_with_specific_commit_id_and_test_commit( |
|
132 | 132 | self, pr_util, no_notifications): |
|
133 | 133 | pull_request = pr_util.create_pull_request() |
|
134 | 134 | pull_request_id = pull_request.pull_request_id |
|
135 | 135 | latest_commit_id = 'test_commit' |
|
136 | 136 | |
|
137 | 137 | # inject additional revision, to fail test the status change on |
|
138 | 138 | # non-latest commit |
|
139 | 139 | pull_request.revisions = pull_request.revisions + ['test_commit'] |
|
140 | 140 | |
|
141 | 141 | id_, params = build_data( |
|
142 | 142 | self.apikey, 'comment_pull_request', |
|
143 | 143 | message='test-change-of-status-not-allowed', |
|
144 | 144 | repoid=pull_request.target_repo.repo_name, |
|
145 | 145 | pullrequestid=pull_request.pull_request_id, |
|
146 | 146 | status='approved', commit_id=latest_commit_id) |
|
147 | 147 | response = api_call(self.app, params) |
|
148 | 148 | pull_request = PullRequestModel().get(pull_request_id) |
|
149 | 149 | comments = CommentsModel().get_comments( |
|
150 | 150 | pull_request.target_repo.repo_id, pull_request=pull_request) |
|
151 | 151 | |
|
152 | 152 | expected = { |
|
153 | 153 | 'pull_request_id': pull_request.pull_request_id, |
|
154 | 154 | 'comment_id': comments[-1].comment_id, |
|
155 | 155 | 'status': {'given': 'approved', 'was_changed': False} |
|
156 | 156 | } |
|
157 | 157 | assert_ok(id_, expected, response.body) |
|
158 | 158 | |
|
159 | 159 | @pytest.mark.backends("git", "hg") |
|
160 | 160 | def test_api_comment_pull_request_change_status_with_specific_commit_id( |
|
161 | 161 | self, pr_util, no_notifications): |
|
162 | 162 | pull_request = pr_util.create_pull_request() |
|
163 | 163 | pull_request_id = pull_request.pull_request_id |
|
164 | 164 | latest_commit_id = pull_request.revisions[0] |
|
165 | 165 | |
|
166 | 166 | id_, params = build_data( |
|
167 | 167 | self.apikey, 'comment_pull_request', |
|
168 | 168 | repoid=pull_request.target_repo.repo_name, |
|
169 | 169 | pullrequestid=pull_request.pull_request_id, |
|
170 | 170 | status='approved', commit_id=latest_commit_id) |
|
171 | 171 | response = api_call(self.app, params) |
|
172 | 172 | pull_request = PullRequestModel().get(pull_request_id) |
|
173 | 173 | |
|
174 | 174 | comments = CommentsModel().get_comments( |
|
175 | 175 | pull_request.target_repo.repo_id, pull_request=pull_request) |
|
176 | 176 | expected = { |
|
177 | 177 | 'pull_request_id': pull_request.pull_request_id, |
|
178 | 178 | 'comment_id': comments[-1].comment_id, |
|
179 | 179 | 'status': {'given': 'approved', 'was_changed': True} |
|
180 | 180 | } |
|
181 | 181 | assert_ok(id_, expected, response.body) |
|
182 | 182 | |
|
183 | 183 | @pytest.mark.backends("git", "hg") |
|
184 | 184 | def test_api_comment_pull_request_missing_params_error(self, pr_util): |
|
185 | 185 | pull_request = pr_util.create_pull_request() |
|
186 | 186 | pull_request_id = pull_request.pull_request_id |
|
187 | 187 | pull_request_repo = pull_request.target_repo.repo_name |
|
188 | 188 | id_, params = build_data( |
|
189 | 189 | self.apikey, 'comment_pull_request', |
|
190 | 190 | repoid=pull_request_repo, |
|
191 | 191 | pullrequestid=pull_request_id) |
|
192 | 192 | response = api_call(self.app, params) |
|
193 | 193 | |
|
194 | 194 | expected = 'Both message and status parameters are missing. At least one is required.' |
|
195 | 195 | assert_error(id_, expected, given=response.body) |
|
196 | 196 | |
|
197 | 197 | @pytest.mark.backends("git", "hg") |
|
198 | 198 | def test_api_comment_pull_request_unknown_status_error(self, pr_util): |
|
199 | 199 | pull_request = pr_util.create_pull_request() |
|
200 | 200 | pull_request_id = pull_request.pull_request_id |
|
201 | 201 | pull_request_repo = pull_request.target_repo.repo_name |
|
202 | 202 | id_, params = build_data( |
|
203 | 203 | self.apikey, 'comment_pull_request', |
|
204 | 204 | repoid=pull_request_repo, |
|
205 | 205 | pullrequestid=pull_request_id, |
|
206 | 206 | status='42') |
|
207 | 207 | response = api_call(self.app, params) |
|
208 | 208 | |
|
209 | 209 | expected = 'Unknown comment status: `42`' |
|
210 | 210 | assert_error(id_, expected, given=response.body) |
|
211 | 211 | |
|
212 | 212 | @pytest.mark.backends("git", "hg") |
|
213 | 213 | def test_api_comment_pull_request_repo_error(self, pr_util): |
|
214 | 214 | pull_request = pr_util.create_pull_request() |
|
215 | 215 | id_, params = build_data( |
|
216 | 216 | self.apikey, 'comment_pull_request', |
|
217 | 217 | repoid=666, pullrequestid=pull_request.pull_request_id) |
|
218 | 218 | response = api_call(self.app, params) |
|
219 | 219 | |
|
220 | 220 | expected = 'repository `666` does not exist' |
|
221 | 221 | assert_error(id_, expected, given=response.body) |
|
222 | 222 | |
|
223 | 223 | @pytest.mark.backends("git", "hg") |
|
224 | 224 | def test_api_comment_pull_request_non_admin_with_userid_error(self, pr_util): |
|
225 | 225 | pull_request = pr_util.create_pull_request() |
|
226 | 226 | id_, params = build_data( |
|
227 | 227 | self.apikey_regular, 'comment_pull_request', |
|
228 | 228 | repoid=pull_request.target_repo.repo_name, |
|
229 | 229 | pullrequestid=pull_request.pull_request_id, |
|
230 | 230 | userid=TEST_USER_ADMIN_LOGIN) |
|
231 | 231 | response = api_call(self.app, params) |
|
232 | 232 | |
|
233 | 233 | expected = 'userid is not the same as your user' |
|
234 | 234 | assert_error(id_, expected, given=response.body) |
|
235 | 235 | |
|
236 | 236 | @pytest.mark.backends("git", "hg") |
|
237 | 237 | def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util): |
|
238 | 238 | pull_request = pr_util.create_pull_request() |
|
239 | 239 | id_, params = build_data( |
|
240 | 240 | self.apikey_regular, 'comment_pull_request', |
|
241 | 241 | repoid=pull_request.target_repo.repo_name, |
|
242 | 242 | status='approved', |
|
243 | 243 | pullrequestid=pull_request.pull_request_id, |
|
244 | 244 | commit_id='XXX') |
|
245 | 245 | response = api_call(self.app, params) |
|
246 | 246 | |
|
247 | 247 | expected = 'Invalid commit_id `XXX` for this pull request.' |
|
248 | 248 | assert_error(id_, expected, given=response.body) |
|
249 | 249 | |
|
250 | 250 | @pytest.mark.backends("git", "hg") |
|
251 | 251 | def test_api_edit_comment(self, pr_util): |
|
252 | 252 | pull_request = pr_util.create_pull_request() |
|
253 | 253 | |
|
254 | 254 | id_, params = build_data( |
|
255 | 255 | self.apikey, |
|
256 | 256 | 'comment_pull_request', |
|
257 | 257 | repoid=pull_request.target_repo.repo_name, |
|
258 | 258 | pullrequestid=pull_request.pull_request_id, |
|
259 | 259 | message='test message', |
|
260 | 260 | ) |
|
261 | 261 | response = api_call(self.app, params) |
|
262 | 262 | json_response = response.json |
|
263 | 263 | comment_id = json_response['result']['comment_id'] |
|
264 | 264 | |
|
265 | 265 | message_after_edit = 'just message' |
|
266 | 266 | id_, params = build_data( |
|
267 | 267 | self.apikey, |
|
268 | 268 | 'edit_comment', |
|
269 | 269 | comment_id=comment_id, |
|
270 | 270 | message=message_after_edit, |
|
271 | 271 | version=0, |
|
272 | 272 | ) |
|
273 | 273 | response = api_call(self.app, params) |
|
274 | 274 | json_response = response.json |
|
275 | 275 | assert json_response['result']['version'] == 1 |
|
276 | 276 | |
|
277 | 277 | text_form_db = ChangesetComment.get(comment_id).text |
|
278 | 278 | assert message_after_edit == text_form_db |
|
279 | 279 | |
|
280 | 280 | @pytest.mark.backends("git", "hg") |
|
281 | 281 | def test_api_edit_comment_wrong_version_mismatch(self, pr_util): |
|
282 | 282 | pull_request = pr_util.create_pull_request() |
|
283 | 283 | |
|
284 | 284 | id_, params = build_data( |
|
285 | 285 | self.apikey, 'comment_pull_request', |
|
286 | 286 | repoid=pull_request.target_repo.repo_name, |
|
287 | 287 | pullrequestid=pull_request.pull_request_id, |
|
288 | 288 | message='test message') |
|
289 | 289 | response = api_call(self.app, params) |
|
290 | 290 | json_response = response.json |
|
291 | 291 | comment_id = json_response['result']['comment_id'] |
|
292 | 292 | |
|
293 | 293 | message_after_edit = 'just message' |
|
294 | 294 | id_, params = build_data( |
|
295 | 295 | self.apikey, |
|
296 | 296 | 'edit_comment', |
|
297 | 297 | comment_id=comment_id, |
|
298 | 298 | message=message_after_edit, |
|
299 | 299 | version=1, |
|
300 | 300 | ) |
|
301 | 301 | response = api_call(self.app, params) |
|
302 | 302 | expected = 'comment ({}) version ({}) mismatch'.format(comment_id, 1) |
|
303 | 303 | assert_error(id_, expected, given=response.body) |
|
304 | 304 | |
|
305 | 305 | @pytest.mark.backends("git", "hg") |
|
306 | 306 | def test_api_edit_comment_wrong_version(self, pr_util): |
|
307 | 307 | pull_request = pr_util.create_pull_request() |
|
308 | 308 | |
|
309 | 309 | id_, params = build_data( |
|
310 | 310 | self.apikey, 'comment_pull_request', |
|
311 | 311 | repoid=pull_request.target_repo.repo_name, |
|
312 | 312 | pullrequestid=pull_request.pull_request_id, |
|
313 | 313 | message='test message') |
|
314 | 314 | response = api_call(self.app, params) |
|
315 | 315 | json_response = response.json |
|
316 | 316 | comment_id = json_response['result']['comment_id'] |
|
317 | 317 | |
|
318 | 318 | id_, params = build_data( |
|
319 | 319 | self.apikey, |
|
320 | 320 | 'edit_comment', |
|
321 | 321 | comment_id=comment_id, |
|
322 | 322 | message='', |
|
323 | 323 | version=0, |
|
324 | 324 | ) |
|
325 | 325 | response = api_call(self.app, params) |
|
326 | 326 | expected = f"comment ({comment_id}) can't be changed with empty string" |
|
327 | 327 | assert_error(id_, expected, given=response.body) |
|
328 | 328 | |
|
329 | 329 | @pytest.mark.backends("git", "hg") |
|
330 | 330 | def test_api_edit_comment_wrong_user_set_by_non_admin(self, pr_util): |
|
331 | 331 | pull_request = pr_util.create_pull_request() |
|
332 | 332 | pull_request_id = pull_request.pull_request_id |
|
333 | 333 | id_, params = build_data( |
|
334 | 334 | self.apikey, |
|
335 | 335 | 'comment_pull_request', |
|
336 | 336 | repoid=pull_request.target_repo.repo_name, |
|
337 | 337 | pullrequestid=pull_request_id, |
|
338 | 338 | message='test message' |
|
339 | 339 | ) |
|
340 | 340 | response = api_call(self.app, params) |
|
341 | 341 | json_response = response.json |
|
342 | 342 | comment_id = json_response['result']['comment_id'] |
|
343 | 343 | |
|
344 | 344 | id_, params = build_data( |
|
345 | 345 | self.apikey_regular, |
|
346 | 346 | 'edit_comment', |
|
347 | 347 | comment_id=comment_id, |
|
348 | 348 | message='just message', |
|
349 | 349 | version=0, |
|
350 | 350 | userid=TEST_USER_ADMIN_LOGIN |
|
351 | 351 | ) |
|
352 | 352 | response = api_call(self.app, params) |
|
353 | 353 | expected = 'userid is not the same as your user' |
|
354 | 354 | assert_error(id_, expected, given=response.body) |
|
355 | 355 | |
|
356 | 356 | @pytest.mark.backends("git", "hg") |
|
357 | 357 | def test_api_edit_comment_wrong_user_with_permissions_to_edit_comment(self, pr_util): |
|
358 | 358 | pull_request = pr_util.create_pull_request() |
|
359 | 359 | pull_request_id = pull_request.pull_request_id |
|
360 | 360 | id_, params = build_data( |
|
361 | 361 | self.apikey, |
|
362 | 362 | 'comment_pull_request', |
|
363 | 363 | repoid=pull_request.target_repo.repo_name, |
|
364 | 364 | pullrequestid=pull_request_id, |
|
365 | 365 | message='test message' |
|
366 | 366 | ) |
|
367 | 367 | response = api_call(self.app, params) |
|
368 | 368 | json_response = response.json |
|
369 | 369 | comment_id = json_response['result']['comment_id'] |
|
370 | 370 | |
|
371 | 371 | id_, params = build_data( |
|
372 | 372 | self.apikey_regular, |
|
373 | 373 | 'edit_comment', |
|
374 | 374 | comment_id=comment_id, |
|
375 | 375 | message='just message', |
|
376 | 376 | version=0, |
|
377 | 377 | ) |
|
378 | 378 | response = api_call(self.app, params) |
|
379 | 379 | expected = "you don't have access to edit this comment" |
|
380 | 380 | assert_error(id_, expected, given=response.body) |
@@ -1,101 +1,101 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.db import Gist |
|
24 | 24 | from rhodecode.model.gist import GistModel |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok, crash) |
|
27 | 27 | from rhodecode.tests.fixture import Fixture |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
31 | 31 | class TestApiCreateGist(object): |
|
32 | 32 | @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [ |
|
33 | 33 | (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC), |
|
34 | 34 | (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE), |
|
35 | 35 | (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC), |
|
36 | 36 | (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE), |
|
37 | 37 | ]) |
|
38 | 38 | def test_api_create_gist(self, lifetime, gist_type, gist_acl_level): |
|
39 | 39 | id_, params = build_data( |
|
40 | 40 | self.apikey_regular, 'create_gist', |
|
41 | 41 | lifetime=lifetime, |
|
42 | 42 | description='foobar-gist', |
|
43 | 43 | gist_type=gist_type, |
|
44 | 44 | acl_level=gist_acl_level, |
|
45 | 45 | files={'foobar_ąć': {'content': 'foo'}}) |
|
46 | 46 | response = api_call(self.app, params) |
|
47 | 47 | response_json = response.json |
|
48 | 48 | gist = response_json['result']['gist'] |
|
49 | 49 | expected = { |
|
50 | 50 | 'gist': { |
|
51 | 51 | 'access_id': gist['access_id'], |
|
52 | 52 | 'created_on': gist['created_on'], |
|
53 | 53 | 'modified_at': gist['modified_at'], |
|
54 | 54 | 'description': 'foobar-gist', |
|
55 | 55 | 'expires': gist['expires'], |
|
56 | 56 | 'gist_id': gist['gist_id'], |
|
57 | 57 | 'type': gist_type, |
|
58 | 58 | 'url': gist['url'], |
|
59 | 59 | # content is empty since we don't show it here |
|
60 | 60 | 'content': None, |
|
61 | 61 | 'acl_level': gist_acl_level, |
|
62 | 62 | }, |
|
63 | 63 | 'msg': 'created new gist' |
|
64 | 64 | } |
|
65 | 65 | try: |
|
66 | 66 | assert_ok(id_, expected, given=response.body) |
|
67 | 67 | finally: |
|
68 | 68 | Fixture().destroy_gists() |
|
69 | 69 | |
|
70 | 70 | @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [ |
|
71 | 71 | ({'gist_type': '"ups" is not one of private, public'}, |
|
72 | 72 | 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}), |
|
73 | 73 | |
|
74 | 74 | ({'lifetime': '-120 is less than minimum value -1'}, |
|
75 | 75 | -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}), |
|
76 | 76 | |
|
77 | 77 | ({'0.content': 'Required'}, |
|
78 | 78 | 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}), |
|
79 | 79 | ]) |
|
80 | 80 | def test_api_try_create_gist( |
|
81 | 81 | self, expected, lifetime, gist_type, gist_acl_level, files): |
|
82 | 82 | id_, params = build_data( |
|
83 | 83 | self.apikey_regular, 'create_gist', |
|
84 | 84 | lifetime=lifetime, |
|
85 | 85 | description='foobar-gist', |
|
86 | 86 | gist_type=gist_type, |
|
87 | 87 | acl_level=gist_acl_level, |
|
88 | 88 | files=files) |
|
89 | 89 | response = api_call(self.app, params) |
|
90 | 90 | |
|
91 | 91 | try: |
|
92 | 92 | assert_error(id_, expected, given=response.body) |
|
93 | 93 | finally: |
|
94 | 94 | Fixture().destroy_gists() |
|
95 | 95 | |
|
96 | 96 | @mock.patch.object(GistModel, 'create', crash) |
|
97 | 97 | def test_api_create_gist_exception_occurred(self): |
|
98 | 98 | id_, params = build_data(self.apikey_regular, 'create_gist', files={}) |
|
99 | 99 | response = api_call(self.app, params) |
|
100 | 100 | expected = 'failed to create gist' |
|
101 | 101 | assert_error(id_, expected, given=response.body) |
@@ -1,367 +1,367 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.db import User |
|
23 | 23 | from rhodecode.model.pull_request import PullRequestModel |
|
24 | 24 | from rhodecode.model.repo import RepoModel |
|
25 | 25 | from rhodecode.model.user import UserModel |
|
26 | 26 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN |
|
27 | 27 | from rhodecode.api.tests.utils import build_data, api_call, assert_error |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
31 | 31 | class TestCreatePullRequestApi(object): |
|
32 | 32 | finalizers = [] |
|
33 | 33 | |
|
34 | 34 | def teardown_method(self, method): |
|
35 | 35 | if self.finalizers: |
|
36 | 36 | for finalizer in self.finalizers: |
|
37 | 37 | finalizer() |
|
38 | 38 | self.finalizers = [] |
|
39 | 39 | |
|
40 | 40 | def test_create_with_wrong_data(self): |
|
41 | 41 | required_data = { |
|
42 | 42 | 'source_repo': 'tests/source_repo', |
|
43 | 43 | 'target_repo': 'tests/target_repo', |
|
44 | 44 | 'source_ref': 'branch:default:initial', |
|
45 | 45 | 'target_ref': 'branch:default:new-feature', |
|
46 | 46 | } |
|
47 | 47 | for key in required_data: |
|
48 | 48 | data = required_data.copy() |
|
49 | 49 | data.pop(key) |
|
50 | 50 | id_, params = build_data( |
|
51 | 51 | self.apikey, 'create_pull_request', **data) |
|
52 | 52 | response = api_call(self.app, params) |
|
53 | 53 | |
|
54 | 54 | expected = 'Missing non optional `{}` arg in JSON DATA'.format(key) |
|
55 | 55 | assert_error(id_, expected, given=response.body) |
|
56 | 56 | |
|
57 | 57 | @pytest.mark.backends("git", "hg") |
|
58 | 58 | @pytest.mark.parametrize('source_ref', [ |
|
59 | 59 | 'bookmarg:default:initial' |
|
60 | 60 | ]) |
|
61 | 61 | def test_create_with_wrong_refs_data(self, backend, source_ref): |
|
62 | 62 | |
|
63 | 63 | data = self._prepare_data(backend) |
|
64 | 64 | data['source_ref'] = source_ref |
|
65 | 65 | |
|
66 | 66 | id_, params = build_data( |
|
67 | 67 | self.apikey_regular, 'create_pull_request', **data) |
|
68 | 68 | |
|
69 | 69 | response = api_call(self.app, params) |
|
70 | 70 | |
|
71 | 71 | expected = "Ref `{}` type is not allowed. " \ |
|
72 | 72 | "Only:['bookmark', 'book', 'tag', 'branch'] " \ |
|
73 | 73 | "are possible.".format(source_ref) |
|
74 | 74 | assert_error(id_, expected, given=response.body) |
|
75 | 75 | |
|
76 | 76 | @pytest.mark.backends("git", "hg") |
|
77 | 77 | def test_create_with_correct_data(self, backend): |
|
78 | 78 | data = self._prepare_data(backend) |
|
79 | 79 | RepoModel().revoke_user_permission( |
|
80 | 80 | self.source.repo_name, User.DEFAULT_USER) |
|
81 | 81 | id_, params = build_data( |
|
82 | 82 | self.apikey_regular, 'create_pull_request', **data) |
|
83 | 83 | response = api_call(self.app, params) |
|
84 | 84 | expected_message = "Created new pull request `{title}`".format( |
|
85 | 85 | title=data['title']) |
|
86 | 86 | result = response.json |
|
87 | 87 | assert result['error'] is None |
|
88 | 88 | assert result['result']['msg'] == expected_message |
|
89 | 89 | pull_request_id = result['result']['pull_request_id'] |
|
90 | 90 | pull_request = PullRequestModel().get(pull_request_id) |
|
91 | 91 | assert pull_request.title == data['title'] |
|
92 | 92 | assert pull_request.description == data['description'] |
|
93 | 93 | assert pull_request.source_ref == data['source_ref'] |
|
94 | 94 | assert pull_request.target_ref == data['target_ref'] |
|
95 | 95 | assert pull_request.source_repo.repo_name == data['source_repo'] |
|
96 | 96 | assert pull_request.target_repo.repo_name == data['target_repo'] |
|
97 | 97 | assert pull_request.revisions == [self.commit_ids['change']] |
|
98 | 98 | assert len(pull_request.reviewers) == 1 |
|
99 | 99 | |
|
100 | 100 | @pytest.mark.backends("git", "hg") |
|
101 | 101 | def test_create_with_empty_description(self, backend): |
|
102 | 102 | data = self._prepare_data(backend) |
|
103 | 103 | data.pop('description') |
|
104 | 104 | id_, params = build_data( |
|
105 | 105 | self.apikey_regular, 'create_pull_request', **data) |
|
106 | 106 | response = api_call(self.app, params) |
|
107 | 107 | expected_message = "Created new pull request `{title}`".format( |
|
108 | 108 | title=data['title']) |
|
109 | 109 | result = response.json |
|
110 | 110 | assert result['error'] is None |
|
111 | 111 | assert result['result']['msg'] == expected_message |
|
112 | 112 | pull_request_id = result['result']['pull_request_id'] |
|
113 | 113 | pull_request = PullRequestModel().get(pull_request_id) |
|
114 | 114 | assert pull_request.description == '' |
|
115 | 115 | |
|
116 | 116 | @pytest.mark.backends("git", "hg") |
|
117 | 117 | def test_create_with_empty_title(self, backend): |
|
118 | 118 | data = self._prepare_data(backend) |
|
119 | 119 | data.pop('title') |
|
120 | 120 | id_, params = build_data( |
|
121 | 121 | self.apikey_regular, 'create_pull_request', **data) |
|
122 | 122 | response = api_call(self.app, params) |
|
123 | 123 | result = response.json |
|
124 | 124 | pull_request_id = result['result']['pull_request_id'] |
|
125 | 125 | pull_request = PullRequestModel().get(pull_request_id) |
|
126 | 126 | data['ref'] = backend.default_branch_name |
|
127 | 127 | title = '{source_repo}#{ref} to {target_repo}'.format(**data) |
|
128 | 128 | assert pull_request.title == title |
|
129 | 129 | |
|
130 | 130 | @pytest.mark.backends("git", "hg") |
|
131 | 131 | def test_create_with_reviewers_specified_by_names( |
|
132 | 132 | self, backend, no_notifications): |
|
133 | 133 | data = self._prepare_data(backend) |
|
134 | 134 | reviewers = [ |
|
135 | 135 | {'username': TEST_USER_REGULAR_LOGIN, |
|
136 | 136 | 'reasons': ['{} added manually'.format(TEST_USER_REGULAR_LOGIN)]}, |
|
137 | 137 | {'username': TEST_USER_ADMIN_LOGIN, |
|
138 | 138 | 'reasons': ['{} added manually'.format(TEST_USER_ADMIN_LOGIN)], |
|
139 | 139 | 'mandatory': True}, |
|
140 | 140 | ] |
|
141 | 141 | data['reviewers'] = reviewers |
|
142 | 142 | |
|
143 | 143 | id_, params = build_data( |
|
144 | 144 | self.apikey_regular, 'create_pull_request', **data) |
|
145 | 145 | response = api_call(self.app, params) |
|
146 | 146 | |
|
147 | 147 | expected_message = "Created new pull request `{title}`".format( |
|
148 | 148 | title=data['title']) |
|
149 | 149 | result = response.json |
|
150 | 150 | assert result['error'] is None |
|
151 | 151 | assert result['result']['msg'] == expected_message |
|
152 | 152 | pull_request_id = result['result']['pull_request_id'] |
|
153 | 153 | pull_request = PullRequestModel().get(pull_request_id) |
|
154 | 154 | |
|
155 | 155 | actual_reviewers = [] |
|
156 | 156 | for rev in pull_request.reviewers: |
|
157 | 157 | entry = { |
|
158 | 158 | 'username': rev.user.username, |
|
159 | 159 | 'reasons': rev.reasons, |
|
160 | 160 | } |
|
161 | 161 | if rev.mandatory: |
|
162 | 162 | entry['mandatory'] = rev.mandatory |
|
163 | 163 | actual_reviewers.append(entry) |
|
164 | 164 | |
|
165 | 165 | owner_username = pull_request.target_repo.user.username |
|
166 | 166 | for spec_reviewer in reviewers[::]: |
|
167 | 167 | # default reviewer will be added who is an owner of the repo |
|
168 | 168 | # this get's overridden by a add owner to reviewers rule |
|
169 | 169 | if spec_reviewer['username'] == owner_username: |
|
170 | 170 | spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner'] |
|
171 | 171 | # since owner is more important, we don't inherit mandatory flag |
|
172 | 172 | del spec_reviewer['mandatory'] |
|
173 | 173 | |
|
174 | 174 | assert sorted(actual_reviewers, key=lambda e: e['username']) \ |
|
175 | 175 | == sorted(reviewers, key=lambda e: e['username']) |
|
176 | 176 | |
|
177 | 177 | @pytest.mark.backends("git", "hg") |
|
178 | 178 | def test_create_with_reviewers_specified_by_ids( |
|
179 | 179 | self, backend, no_notifications): |
|
180 | 180 | data = self._prepare_data(backend) |
|
181 | 181 | reviewers = [ |
|
182 | 182 | {'username': UserModel().get_by_username( |
|
183 | 183 | TEST_USER_REGULAR_LOGIN).user_id, |
|
184 | 184 | 'reasons': ['added manually']}, |
|
185 | 185 | {'username': UserModel().get_by_username( |
|
186 | 186 | TEST_USER_ADMIN_LOGIN).user_id, |
|
187 | 187 | 'reasons': ['added manually']}, |
|
188 | 188 | ] |
|
189 | 189 | |
|
190 | 190 | data['reviewers'] = reviewers |
|
191 | 191 | id_, params = build_data( |
|
192 | 192 | self.apikey_regular, 'create_pull_request', **data) |
|
193 | 193 | response = api_call(self.app, params) |
|
194 | 194 | |
|
195 | 195 | expected_message = "Created new pull request `{title}`".format( |
|
196 | 196 | title=data['title']) |
|
197 | 197 | result = response.json |
|
198 | 198 | assert result['error'] is None |
|
199 | 199 | assert result['result']['msg'] == expected_message |
|
200 | 200 | pull_request_id = result['result']['pull_request_id'] |
|
201 | 201 | pull_request = PullRequestModel().get(pull_request_id) |
|
202 | 202 | |
|
203 | 203 | actual_reviewers = [] |
|
204 | 204 | for rev in pull_request.reviewers: |
|
205 | 205 | entry = { |
|
206 | 206 | 'username': rev.user.user_id, |
|
207 | 207 | 'reasons': rev.reasons, |
|
208 | 208 | } |
|
209 | 209 | if rev.mandatory: |
|
210 | 210 | entry['mandatory'] = rev.mandatory |
|
211 | 211 | actual_reviewers.append(entry) |
|
212 | 212 | |
|
213 | 213 | owner_user_id = pull_request.target_repo.user.user_id |
|
214 | 214 | for spec_reviewer in reviewers[::]: |
|
215 | 215 | # default reviewer will be added who is an owner of the repo |
|
216 | 216 | # this get's overridden by a add owner to reviewers rule |
|
217 | 217 | if spec_reviewer['username'] == owner_user_id: |
|
218 | 218 | spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner'] |
|
219 | 219 | |
|
220 | 220 | assert sorted(actual_reviewers, key=lambda e: e['username']) \ |
|
221 | 221 | == sorted(reviewers, key=lambda e: e['username']) |
|
222 | 222 | |
|
223 | 223 | @pytest.mark.backends("git", "hg") |
|
224 | 224 | def test_create_fails_when_the_reviewer_is_not_found(self, backend): |
|
225 | 225 | data = self._prepare_data(backend) |
|
226 | 226 | data['reviewers'] = [{'username': 'somebody'}] |
|
227 | 227 | id_, params = build_data( |
|
228 | 228 | self.apikey_regular, 'create_pull_request', **data) |
|
229 | 229 | response = api_call(self.app, params) |
|
230 | 230 | expected_message = 'user `somebody` does not exist' |
|
231 | 231 | assert_error(id_, expected_message, given=response.body) |
|
232 | 232 | |
|
233 | 233 | @pytest.mark.backends("git", "hg") |
|
234 | 234 | def test_cannot_create_with_reviewers_in_wrong_format(self, backend): |
|
235 | 235 | data = self._prepare_data(backend) |
|
236 | 236 | reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN]) |
|
237 | 237 | data['reviewers'] = reviewers |
|
238 | 238 | id_, params = build_data( |
|
239 | 239 | self.apikey_regular, 'create_pull_request', **data) |
|
240 | 240 | response = api_call(self.app, params) |
|
241 | 241 | expected_message = {u'': '"test_regular,test_admin" is not iterable'} |
|
242 | 242 | assert_error(id_, expected_message, given=response.body) |
|
243 | 243 | |
|
244 | 244 | @pytest.mark.backends("git", "hg") |
|
245 | 245 | def test_create_with_no_commit_hashes(self, backend): |
|
246 | 246 | data = self._prepare_data(backend) |
|
247 | 247 | expected_source_ref = data['source_ref'] |
|
248 | 248 | expected_target_ref = data['target_ref'] |
|
249 | 249 | data['source_ref'] = 'branch:{}'.format(backend.default_branch_name) |
|
250 | 250 | data['target_ref'] = 'branch:{}'.format(backend.default_branch_name) |
|
251 | 251 | id_, params = build_data( |
|
252 | 252 | self.apikey_regular, 'create_pull_request', **data) |
|
253 | 253 | response = api_call(self.app, params) |
|
254 | 254 | expected_message = "Created new pull request `{title}`".format( |
|
255 | 255 | title=data['title']) |
|
256 | 256 | result = response.json |
|
257 | 257 | assert result['result']['msg'] == expected_message |
|
258 | 258 | pull_request_id = result['result']['pull_request_id'] |
|
259 | 259 | pull_request = PullRequestModel().get(pull_request_id) |
|
260 | 260 | assert pull_request.source_ref == expected_source_ref |
|
261 | 261 | assert pull_request.target_ref == expected_target_ref |
|
262 | 262 | |
|
263 | 263 | @pytest.mark.backends("git", "hg") |
|
264 | 264 | @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"]) |
|
265 | 265 | def test_create_fails_with_wrong_repo(self, backend, data_key): |
|
266 | 266 | repo_name = 'fake-repo' |
|
267 | 267 | data = self._prepare_data(backend) |
|
268 | 268 | data[data_key] = repo_name |
|
269 | 269 | id_, params = build_data( |
|
270 | 270 | self.apikey_regular, 'create_pull_request', **data) |
|
271 | 271 | response = api_call(self.app, params) |
|
272 | 272 | expected_message = 'repository `{}` does not exist'.format(repo_name) |
|
273 | 273 | assert_error(id_, expected_message, given=response.body) |
|
274 | 274 | |
|
275 | 275 | @pytest.mark.backends("git", "hg") |
|
276 | 276 | @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"]) |
|
277 | 277 | def test_create_fails_with_non_existing_branch(self, backend, data_key): |
|
278 | 278 | branch_name = 'test-branch' |
|
279 | 279 | data = self._prepare_data(backend) |
|
280 | 280 | data[data_key] = "branch:{}".format(branch_name) |
|
281 | 281 | id_, params = build_data( |
|
282 | 282 | self.apikey_regular, 'create_pull_request', **data) |
|
283 | 283 | response = api_call(self.app, params) |
|
284 | 284 | expected_message = 'The specified value:{type}:`{name}` ' \ |
|
285 | 285 | 'does not exist, or is not allowed.'.format(type='branch', |
|
286 | 286 | name=branch_name) |
|
287 | 287 | assert_error(id_, expected_message, given=response.body) |
|
288 | 288 | |
|
289 | 289 | @pytest.mark.backends("git", "hg") |
|
290 | 290 | @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"]) |
|
291 | 291 | def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key): |
|
292 | 292 | data = self._prepare_data(backend) |
|
293 | 293 | ref = 'stange-ref' |
|
294 | 294 | data[data_key] = ref |
|
295 | 295 | id_, params = build_data( |
|
296 | 296 | self.apikey_regular, 'create_pull_request', **data) |
|
297 | 297 | response = api_call(self.app, params) |
|
298 | 298 | expected_message = ( |
|
299 | 299 | 'Ref `{ref}` given in a wrong format. Please check the API' |
|
300 | 300 | ' documentation for more details'.format(ref=ref)) |
|
301 | 301 | assert_error(id_, expected_message, given=response.body) |
|
302 | 302 | |
|
303 | 303 | @pytest.mark.backends("git", "hg") |
|
304 | 304 | @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"]) |
|
305 | 305 | def test_create_fails_with_non_existing_ref(self, backend, data_key): |
|
306 | 306 | commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10' |
|
307 | 307 | ref = self._get_full_ref(backend, commit_id) |
|
308 | 308 | data = self._prepare_data(backend) |
|
309 | 309 | data[data_key] = ref |
|
310 | 310 | id_, params = build_data( |
|
311 | 311 | self.apikey_regular, 'create_pull_request', **data) |
|
312 | 312 | response = api_call(self.app, params) |
|
313 | 313 | expected_message = 'Ref `{}` does not exist'.format(ref) |
|
314 | 314 | assert_error(id_, expected_message, given=response.body) |
|
315 | 315 | |
|
316 | 316 | @pytest.mark.backends("git", "hg") |
|
317 | 317 | def test_create_fails_when_no_revisions(self, backend): |
|
318 | 318 | data = self._prepare_data(backend, source_head='initial') |
|
319 | 319 | id_, params = build_data( |
|
320 | 320 | self.apikey_regular, 'create_pull_request', **data) |
|
321 | 321 | response = api_call(self.app, params) |
|
322 | 322 | expected_message = 'no commits found for merge between specified references' |
|
323 | 323 | assert_error(id_, expected_message, given=response.body) |
|
324 | 324 | |
|
325 | 325 | @pytest.mark.backends("git", "hg") |
|
326 | 326 | def test_create_fails_when_no_permissions(self, backend): |
|
327 | 327 | data = self._prepare_data(backend) |
|
328 | 328 | RepoModel().revoke_user_permission( |
|
329 | 329 | self.source.repo_name, self.test_user) |
|
330 | 330 | RepoModel().revoke_user_permission( |
|
331 | 331 | self.source.repo_name, User.DEFAULT_USER) |
|
332 | 332 | |
|
333 | 333 | id_, params = build_data( |
|
334 | 334 | self.apikey_regular, 'create_pull_request', **data) |
|
335 | 335 | response = api_call(self.app, params) |
|
336 | 336 | expected_message = 'repository `{}` does not exist'.format( |
|
337 | 337 | self.source.repo_name) |
|
338 | 338 | assert_error(id_, expected_message, given=response.body) |
|
339 | 339 | |
|
340 | 340 | def _prepare_data( |
|
341 | 341 | self, backend, source_head='change', target_head='initial'): |
|
342 | 342 | commits = [ |
|
343 | 343 | {'message': 'initial'}, |
|
344 | 344 | {'message': 'change'}, |
|
345 | 345 | {'message': 'new-feature', 'parents': ['initial']}, |
|
346 | 346 | ] |
|
347 | 347 | self.commit_ids = backend.create_master_repo(commits) |
|
348 | 348 | self.source = backend.create_repo(heads=[source_head]) |
|
349 | 349 | self.target = backend.create_repo(heads=[target_head]) |
|
350 | 350 | |
|
351 | 351 | data = { |
|
352 | 352 | 'source_repo': self.source.repo_name, |
|
353 | 353 | 'target_repo': self.target.repo_name, |
|
354 | 354 | 'source_ref': self._get_full_ref( |
|
355 | 355 | backend, self.commit_ids[source_head]), |
|
356 | 356 | 'target_ref': self._get_full_ref( |
|
357 | 357 | backend, self.commit_ids[target_head]), |
|
358 | 358 | 'title': 'Test PR 1', |
|
359 | 359 | 'description': 'Test' |
|
360 | 360 | } |
|
361 | 361 | RepoModel().grant_user_permission( |
|
362 | 362 | self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read') |
|
363 | 363 | return data |
|
364 | 364 | |
|
365 | 365 | def _get_full_ref(self, backend, commit_id): |
|
366 | 366 | return 'branch:{branch}:{commit_id}'.format( |
|
367 | 367 | branch=backend.default_branch_name, commit_id=commit_id) |
@@ -1,348 +1,348 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.lib.vcs import settings |
|
24 | 24 | from rhodecode.model.meta import Session |
|
25 | 25 | from rhodecode.model.repo import RepoModel |
|
26 | 26 | from rhodecode.model.user import UserModel |
|
27 | 27 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
28 | 28 | from rhodecode.api.tests.utils import ( |
|
29 | 29 | build_data, api_call, assert_ok, assert_error, crash) |
|
30 | 30 | from rhodecode.tests.fixture import Fixture |
|
31 | 31 | from rhodecode.lib.ext_json import json |
|
32 | 32 | from rhodecode.lib.str_utils import safe_str |
|
33 | 33 | |
|
34 | 34 | |
|
35 | 35 | fixture = Fixture() |
|
36 | 36 | |
|
37 | 37 | |
|
38 | 38 | @pytest.mark.usefixtures("testuser_api", "app") |
|
39 | 39 | class TestCreateRepo(object): |
|
40 | 40 | |
|
41 | 41 | @pytest.mark.parametrize('given, expected_name, expected_exc', [ |
|
42 | 42 | ('api repo-1', 'api-repo-1', False), |
|
43 | 43 | ('api-repo 1-ąć', 'api-repo-1-ąć', False), |
|
44 | 44 | (u'unicode-ąć', u'unicode-ąć', False), |
|
45 | 45 | ('some repo v1.2', 'some-repo-v1.2', False), |
|
46 | 46 | ('v2.0', 'v2.0', False), |
|
47 | 47 | ]) |
|
48 | 48 | def test_api_create_repo(self, backend, given, expected_name, expected_exc): |
|
49 | 49 | |
|
50 | 50 | id_, params = build_data( |
|
51 | 51 | self.apikey, |
|
52 | 52 | 'create_repo', |
|
53 | 53 | repo_name=given, |
|
54 | 54 | owner=TEST_USER_ADMIN_LOGIN, |
|
55 | 55 | repo_type=backend.alias, |
|
56 | 56 | ) |
|
57 | 57 | response = api_call(self.app, params) |
|
58 | 58 | |
|
59 | 59 | ret = { |
|
60 | 60 | 'msg': 'Created new repository `%s`' % (expected_name,), |
|
61 | 61 | 'success': True, |
|
62 | 62 | 'task': None, |
|
63 | 63 | } |
|
64 | 64 | expected = ret |
|
65 | 65 | assert_ok(id_, expected, given=response.body) |
|
66 | 66 | |
|
67 | 67 | repo = RepoModel().get_by_repo_name(safe_str(expected_name)) |
|
68 | 68 | assert repo is not None |
|
69 | 69 | |
|
70 | 70 | id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name) |
|
71 | 71 | response = api_call(self.app, params) |
|
72 | 72 | body = json.loads(response.body) |
|
73 | 73 | |
|
74 | 74 | assert body['result']['enable_downloads'] is False |
|
75 | 75 | assert body['result']['enable_locking'] is False |
|
76 | 76 | assert body['result']['enable_statistics'] is False |
|
77 | 77 | |
|
78 | 78 | fixture.destroy_repo(safe_str(expected_name)) |
|
79 | 79 | |
|
80 | 80 | def test_api_create_restricted_repo_type(self, backend): |
|
81 | 81 | repo_name = 'api-repo-type-{0}'.format(backend.alias) |
|
82 | 82 | id_, params = build_data( |
|
83 | 83 | self.apikey, |
|
84 | 84 | 'create_repo', |
|
85 | 85 | repo_name=repo_name, |
|
86 | 86 | owner=TEST_USER_ADMIN_LOGIN, |
|
87 | 87 | repo_type=backend.alias, |
|
88 | 88 | ) |
|
89 | 89 | git_backend = settings.BACKENDS['git'] |
|
90 | 90 | with mock.patch( |
|
91 | 91 | 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}): |
|
92 | 92 | response = api_call(self.app, params) |
|
93 | 93 | |
|
94 | 94 | repo = RepoModel().get_by_repo_name(repo_name) |
|
95 | 95 | |
|
96 | 96 | if backend.alias == 'git': |
|
97 | 97 | assert repo is not None |
|
98 | 98 | expected = { |
|
99 | 99 | 'msg': 'Created new repository `{0}`'.format(repo_name,), |
|
100 | 100 | 'success': True, |
|
101 | 101 | 'task': None, |
|
102 | 102 | } |
|
103 | 103 | assert_ok(id_, expected, given=response.body) |
|
104 | 104 | else: |
|
105 | 105 | assert repo is None |
|
106 | 106 | |
|
107 | 107 | fixture.destroy_repo(repo_name) |
|
108 | 108 | |
|
109 | 109 | def test_api_create_repo_with_booleans(self, backend): |
|
110 | 110 | repo_name = 'api-repo-2' |
|
111 | 111 | id_, params = build_data( |
|
112 | 112 | self.apikey, |
|
113 | 113 | 'create_repo', |
|
114 | 114 | repo_name=repo_name, |
|
115 | 115 | owner=TEST_USER_ADMIN_LOGIN, |
|
116 | 116 | repo_type=backend.alias, |
|
117 | 117 | enable_statistics=True, |
|
118 | 118 | enable_locking=True, |
|
119 | 119 | enable_downloads=True |
|
120 | 120 | ) |
|
121 | 121 | response = api_call(self.app, params) |
|
122 | 122 | |
|
123 | 123 | repo = RepoModel().get_by_repo_name(repo_name) |
|
124 | 124 | |
|
125 | 125 | assert repo is not None |
|
126 | 126 | ret = { |
|
127 | 127 | 'msg': 'Created new repository `%s`' % (repo_name,), |
|
128 | 128 | 'success': True, |
|
129 | 129 | 'task': None, |
|
130 | 130 | } |
|
131 | 131 | expected = ret |
|
132 | 132 | assert_ok(id_, expected, given=response.body) |
|
133 | 133 | |
|
134 | 134 | id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name) |
|
135 | 135 | response = api_call(self.app, params) |
|
136 | 136 | body = json.loads(response.body) |
|
137 | 137 | |
|
138 | 138 | assert body['result']['enable_downloads'] is True |
|
139 | 139 | assert body['result']['enable_locking'] is True |
|
140 | 140 | assert body['result']['enable_statistics'] is True |
|
141 | 141 | |
|
142 | 142 | fixture.destroy_repo(repo_name) |
|
143 | 143 | |
|
144 | 144 | def test_api_create_repo_in_group(self, backend): |
|
145 | 145 | repo_group_name = 'my_gr' |
|
146 | 146 | # create the parent |
|
147 | 147 | fixture.create_repo_group(repo_group_name) |
|
148 | 148 | |
|
149 | 149 | repo_name = '%s/api-repo-gr' % (repo_group_name,) |
|
150 | 150 | id_, params = build_data( |
|
151 | 151 | self.apikey, 'create_repo', |
|
152 | 152 | repo_name=repo_name, |
|
153 | 153 | owner=TEST_USER_ADMIN_LOGIN, |
|
154 | 154 | repo_type=backend.alias,) |
|
155 | 155 | response = api_call(self.app, params) |
|
156 | 156 | repo = RepoModel().get_by_repo_name(repo_name) |
|
157 | 157 | assert repo is not None |
|
158 | 158 | assert repo.group is not None |
|
159 | 159 | |
|
160 | 160 | ret = { |
|
161 | 161 | 'msg': 'Created new repository `%s`' % (repo_name,), |
|
162 | 162 | 'success': True, |
|
163 | 163 | 'task': None, |
|
164 | 164 | } |
|
165 | 165 | expected = ret |
|
166 | 166 | assert_ok(id_, expected, given=response.body) |
|
167 | 167 | fixture.destroy_repo(repo_name) |
|
168 | 168 | fixture.destroy_repo_group(repo_group_name) |
|
169 | 169 | |
|
170 | 170 | def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util): |
|
171 | 171 | repo_group_name = 'fake_group' |
|
172 | 172 | |
|
173 | 173 | repo_name = '%s/api-repo-gr' % (repo_group_name,) |
|
174 | 174 | id_, params = build_data( |
|
175 | 175 | self.apikey, 'create_repo', |
|
176 | 176 | repo_name=repo_name, |
|
177 | 177 | owner=TEST_USER_ADMIN_LOGIN, |
|
178 | 178 | repo_type=backend.alias,) |
|
179 | 179 | response = api_call(self.app, params) |
|
180 | 180 | |
|
181 | 181 | expected = {'repo_group': 'Repository group `{}` does not exist'.format( |
|
182 | 182 | repo_group_name)} |
|
183 | 183 | assert_error(id_, expected, given=response.body) |
|
184 | 184 | |
|
185 | 185 | def test_api_create_repo_unknown_owner(self, backend): |
|
186 | 186 | repo_name = 'api-repo-2' |
|
187 | 187 | owner = 'i-dont-exist' |
|
188 | 188 | id_, params = build_data( |
|
189 | 189 | self.apikey, 'create_repo', |
|
190 | 190 | repo_name=repo_name, |
|
191 | 191 | owner=owner, |
|
192 | 192 | repo_type=backend.alias) |
|
193 | 193 | response = api_call(self.app, params) |
|
194 | 194 | expected = 'user `%s` does not exist' % (owner,) |
|
195 | 195 | assert_error(id_, expected, given=response.body) |
|
196 | 196 | |
|
197 | 197 | def test_api_create_repo_dont_specify_owner(self, backend): |
|
198 | 198 | repo_name = 'api-repo-3' |
|
199 | 199 | id_, params = build_data( |
|
200 | 200 | self.apikey, 'create_repo', |
|
201 | 201 | repo_name=repo_name, |
|
202 | 202 | repo_type=backend.alias) |
|
203 | 203 | response = api_call(self.app, params) |
|
204 | 204 | |
|
205 | 205 | repo = RepoModel().get_by_repo_name(repo_name) |
|
206 | 206 | assert repo is not None |
|
207 | 207 | ret = { |
|
208 | 208 | 'msg': 'Created new repository `%s`' % (repo_name,), |
|
209 | 209 | 'success': True, |
|
210 | 210 | 'task': None, |
|
211 | 211 | } |
|
212 | 212 | expected = ret |
|
213 | 213 | assert_ok(id_, expected, given=response.body) |
|
214 | 214 | fixture.destroy_repo(repo_name) |
|
215 | 215 | |
|
216 | 216 | def test_api_create_repo_by_non_admin(self, backend): |
|
217 | 217 | repo_name = 'api-repo-4' |
|
218 | 218 | id_, params = build_data( |
|
219 | 219 | self.apikey_regular, 'create_repo', |
|
220 | 220 | repo_name=repo_name, |
|
221 | 221 | repo_type=backend.alias) |
|
222 | 222 | response = api_call(self.app, params) |
|
223 | 223 | |
|
224 | 224 | repo = RepoModel().get_by_repo_name(repo_name) |
|
225 | 225 | assert repo is not None |
|
226 | 226 | ret = { |
|
227 | 227 | 'msg': 'Created new repository `%s`' % (repo_name,), |
|
228 | 228 | 'success': True, |
|
229 | 229 | 'task': None, |
|
230 | 230 | } |
|
231 | 231 | expected = ret |
|
232 | 232 | assert_ok(id_, expected, given=response.body) |
|
233 | 233 | fixture.destroy_repo(repo_name) |
|
234 | 234 | |
|
235 | 235 | def test_api_create_repo_by_non_admin_specify_owner(self, backend): |
|
236 | 236 | repo_name = 'api-repo-5' |
|
237 | 237 | owner = 'i-dont-exist' |
|
238 | 238 | id_, params = build_data( |
|
239 | 239 | self.apikey_regular, 'create_repo', |
|
240 | 240 | repo_name=repo_name, |
|
241 | 241 | repo_type=backend.alias, |
|
242 | 242 | owner=owner) |
|
243 | 243 | response = api_call(self.app, params) |
|
244 | 244 | |
|
245 | 245 | expected = 'Only RhodeCode super-admin can specify `owner` param' |
|
246 | 246 | assert_error(id_, expected, given=response.body) |
|
247 | 247 | fixture.destroy_repo(repo_name) |
|
248 | 248 | |
|
249 | 249 | def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend): |
|
250 | 250 | repo_group_name = 'no-access' |
|
251 | 251 | fixture.create_repo_group(repo_group_name) |
|
252 | 252 | repo_name = 'no-access/api-repo' |
|
253 | 253 | |
|
254 | 254 | id_, params = build_data( |
|
255 | 255 | self.apikey_regular, 'create_repo', |
|
256 | 256 | repo_name=repo_name, |
|
257 | 257 | repo_type=backend.alias) |
|
258 | 258 | response = api_call(self.app, params) |
|
259 | 259 | |
|
260 | 260 | expected = {'repo_group': 'Repository group `{}` does not exist'.format( |
|
261 | 261 | repo_group_name)} |
|
262 | 262 | assert_error(id_, expected, given=response.body) |
|
263 | 263 | fixture.destroy_repo_group(repo_group_name) |
|
264 | 264 | fixture.destroy_repo(repo_name) |
|
265 | 265 | |
|
266 | 266 | def test_api_create_repo_non_admin_no_permission_to_create_to_root_level( |
|
267 | 267 | self, backend, user_util): |
|
268 | 268 | |
|
269 | 269 | regular_user = user_util.create_user() |
|
270 | 270 | regular_user_api_key = regular_user.api_key |
|
271 | 271 | |
|
272 | 272 | usr = UserModel().get_by_username(regular_user.username) |
|
273 | 273 | usr.inherit_default_permissions = False |
|
274 | 274 | Session().add(usr) |
|
275 | 275 | |
|
276 | 276 | repo_name = backend.new_repo_name() |
|
277 | 277 | id_, params = build_data( |
|
278 | 278 | regular_user_api_key, 'create_repo', |
|
279 | 279 | repo_name=repo_name, |
|
280 | 280 | repo_type=backend.alias) |
|
281 | 281 | response = api_call(self.app, params) |
|
282 | 282 | expected = { |
|
283 | 283 | "repo_name": "You do not have the permission to " |
|
284 | 284 | "store repositories in the root location."} |
|
285 | 285 | assert_error(id_, expected, given=response.body) |
|
286 | 286 | |
|
287 | 287 | def test_api_create_repo_exists(self, backend): |
|
288 | 288 | repo_name = backend.repo_name |
|
289 | 289 | id_, params = build_data( |
|
290 | 290 | self.apikey, 'create_repo', |
|
291 | 291 | repo_name=repo_name, |
|
292 | 292 | owner=TEST_USER_ADMIN_LOGIN, |
|
293 | 293 | repo_type=backend.alias,) |
|
294 | 294 | response = api_call(self.app, params) |
|
295 | 295 | expected = { |
|
296 | 296 | 'unique_repo_name': 'Repository with name `{}` already exists'.format( |
|
297 | 297 | repo_name)} |
|
298 | 298 | assert_error(id_, expected, given=response.body) |
|
299 | 299 | |
|
300 | 300 | @mock.patch.object(RepoModel, 'create', crash) |
|
301 | 301 | def test_api_create_repo_exception_occurred(self, backend): |
|
302 | 302 | repo_name = 'api-repo-6' |
|
303 | 303 | id_, params = build_data( |
|
304 | 304 | self.apikey, 'create_repo', |
|
305 | 305 | repo_name=repo_name, |
|
306 | 306 | owner=TEST_USER_ADMIN_LOGIN, |
|
307 | 307 | repo_type=backend.alias,) |
|
308 | 308 | response = api_call(self.app, params) |
|
309 | 309 | expected = 'failed to create repository `%s`' % (repo_name,) |
|
310 | 310 | assert_error(id_, expected, given=response.body) |
|
311 | 311 | |
|
312 | 312 | @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [ |
|
313 | 313 | (None, 'foo bar x', 'foo-bar-x'), |
|
314 | 314 | ('foo', '/foo//bar x', 'foo/bar-x'), |
|
315 | 315 | ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'), |
|
316 | 316 | ]) |
|
317 | 317 | def test_create_repo_with_extra_slashes_in_name( |
|
318 | 318 | self, backend, parent_group, dirty_name, expected_name): |
|
319 | 319 | |
|
320 | 320 | if parent_group: |
|
321 | 321 | gr = fixture.create_repo_group(parent_group) |
|
322 | 322 | assert gr.group_name == parent_group |
|
323 | 323 | |
|
324 | 324 | id_, params = build_data( |
|
325 | 325 | self.apikey, 'create_repo', |
|
326 | 326 | repo_name=dirty_name, |
|
327 | 327 | repo_type=backend.alias, |
|
328 | 328 | owner=TEST_USER_ADMIN_LOGIN,) |
|
329 | 329 | response = api_call(self.app, params) |
|
330 | 330 | expected ={ |
|
331 | 331 | "msg": "Created new repository `{}`".format(expected_name), |
|
332 | 332 | "task": None, |
|
333 | 333 | "success": True |
|
334 | 334 | } |
|
335 | 335 | assert_ok(id_, expected, response.body) |
|
336 | 336 | |
|
337 | 337 | repo = RepoModel().get_by_repo_name(expected_name) |
|
338 | 338 | assert repo is not None |
|
339 | 339 | |
|
340 | 340 | expected = { |
|
341 | 341 | 'msg': 'Created new repository `%s`' % (expected_name,), |
|
342 | 342 | 'success': True, |
|
343 | 343 | 'task': None, |
|
344 | 344 | } |
|
345 | 345 | assert_ok(id_, expected, given=response.body) |
|
346 | 346 | fixture.destroy_repo(expected_name) |
|
347 | 347 | if parent_group: |
|
348 | 348 | fixture.destroy_repo_group(parent_group) |
@@ -1,288 +1,288 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.meta import Session |
|
24 | 24 | from rhodecode.model.repo_group import RepoGroupModel |
|
25 | 25 | from rhodecode.model.user import UserModel |
|
26 | 26 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
27 | 27 | from rhodecode.api.tests.utils import ( |
|
28 | 28 | build_data, api_call, assert_ok, assert_error, crash) |
|
29 | 29 | from rhodecode.tests.fixture import Fixture |
|
30 | 30 | |
|
31 | 31 | |
|
32 | 32 | fixture = Fixture() |
|
33 | 33 | |
|
34 | 34 | |
|
35 | 35 | @pytest.mark.usefixtures("testuser_api", "app") |
|
36 | 36 | class TestCreateRepoGroup(object): |
|
37 | 37 | def test_api_create_repo_group(self): |
|
38 | 38 | repo_group_name = 'api-repo-group' |
|
39 | 39 | |
|
40 | 40 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
41 | 41 | assert repo_group is None |
|
42 | 42 | |
|
43 | 43 | id_, params = build_data( |
|
44 | 44 | self.apikey, 'create_repo_group', |
|
45 | 45 | group_name=repo_group_name, |
|
46 | 46 | owner=TEST_USER_ADMIN_LOGIN,) |
|
47 | 47 | response = api_call(self.app, params) |
|
48 | 48 | |
|
49 | 49 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
50 | 50 | assert repo_group is not None |
|
51 | 51 | ret = { |
|
52 | 52 | 'msg': 'Created new repo group `%s`' % (repo_group_name,), |
|
53 | 53 | 'repo_group': repo_group.get_api_data() |
|
54 | 54 | } |
|
55 | 55 | expected = ret |
|
56 | 56 | try: |
|
57 | 57 | assert_ok(id_, expected, given=response.body) |
|
58 | 58 | finally: |
|
59 | 59 | fixture.destroy_repo_group(repo_group_name) |
|
60 | 60 | |
|
61 | 61 | def test_api_create_repo_group_in_another_group(self): |
|
62 | 62 | repo_group_name = 'api-repo-group' |
|
63 | 63 | |
|
64 | 64 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
65 | 65 | assert repo_group is None |
|
66 | 66 | # create the parent |
|
67 | 67 | fixture.create_repo_group(repo_group_name) |
|
68 | 68 | |
|
69 | 69 | full_repo_group_name = repo_group_name+'/'+repo_group_name |
|
70 | 70 | id_, params = build_data( |
|
71 | 71 | self.apikey, 'create_repo_group', |
|
72 | 72 | group_name=full_repo_group_name, |
|
73 | 73 | owner=TEST_USER_ADMIN_LOGIN, |
|
74 | 74 | copy_permissions=True) |
|
75 | 75 | response = api_call(self.app, params) |
|
76 | 76 | |
|
77 | 77 | repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name) |
|
78 | 78 | assert repo_group is not None |
|
79 | 79 | ret = { |
|
80 | 80 | 'msg': 'Created new repo group `%s`' % (full_repo_group_name,), |
|
81 | 81 | 'repo_group': repo_group.get_api_data() |
|
82 | 82 | } |
|
83 | 83 | expected = ret |
|
84 | 84 | try: |
|
85 | 85 | assert_ok(id_, expected, given=response.body) |
|
86 | 86 | finally: |
|
87 | 87 | fixture.destroy_repo_group(full_repo_group_name) |
|
88 | 88 | fixture.destroy_repo_group(repo_group_name) |
|
89 | 89 | |
|
90 | 90 | def test_api_create_repo_group_in_another_group_not_existing(self): |
|
91 | 91 | repo_group_name = 'api-repo-group-no' |
|
92 | 92 | |
|
93 | 93 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
94 | 94 | assert repo_group is None |
|
95 | 95 | |
|
96 | 96 | full_repo_group_name = repo_group_name+'/'+repo_group_name |
|
97 | 97 | id_, params = build_data( |
|
98 | 98 | self.apikey, 'create_repo_group', |
|
99 | 99 | group_name=full_repo_group_name, |
|
100 | 100 | owner=TEST_USER_ADMIN_LOGIN, |
|
101 | 101 | copy_permissions=True) |
|
102 | 102 | response = api_call(self.app, params) |
|
103 | 103 | expected = { |
|
104 | 104 | 'repo_group': |
|
105 | 105 | 'Parent repository group `{}` does not exist'.format( |
|
106 | 106 | repo_group_name)} |
|
107 | 107 | assert_error(id_, expected, given=response.body) |
|
108 | 108 | |
|
109 | 109 | def test_api_create_repo_group_that_exists(self): |
|
110 | 110 | repo_group_name = 'api-repo-group' |
|
111 | 111 | |
|
112 | 112 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
113 | 113 | assert repo_group is None |
|
114 | 114 | |
|
115 | 115 | fixture.create_repo_group(repo_group_name) |
|
116 | 116 | id_, params = build_data( |
|
117 | 117 | self.apikey, 'create_repo_group', |
|
118 | 118 | group_name=repo_group_name, |
|
119 | 119 | owner=TEST_USER_ADMIN_LOGIN,) |
|
120 | 120 | response = api_call(self.app, params) |
|
121 | 121 | expected = { |
|
122 | 122 | 'unique_repo_group_name': |
|
123 | 123 | 'Repository group with name `{}` already exists'.format( |
|
124 | 124 | repo_group_name)} |
|
125 | 125 | try: |
|
126 | 126 | assert_error(id_, expected, given=response.body) |
|
127 | 127 | finally: |
|
128 | 128 | fixture.destroy_repo_group(repo_group_name) |
|
129 | 129 | |
|
130 | 130 | def test_api_create_repo_group_regular_user_wit_root_location_perms( |
|
131 | 131 | self, user_util): |
|
132 | 132 | regular_user = user_util.create_user() |
|
133 | 133 | regular_user_api_key = regular_user.api_key |
|
134 | 134 | |
|
135 | 135 | repo_group_name = 'api-repo-group-by-regular-user' |
|
136 | 136 | |
|
137 | 137 | usr = UserModel().get_by_username(regular_user.username) |
|
138 | 138 | usr.inherit_default_permissions = False |
|
139 | 139 | Session().add(usr) |
|
140 | 140 | |
|
141 | 141 | UserModel().grant_perm( |
|
142 | 142 | regular_user.username, 'hg.repogroup.create.true') |
|
143 | 143 | Session().commit() |
|
144 | 144 | |
|
145 | 145 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
146 | 146 | assert repo_group is None |
|
147 | 147 | |
|
148 | 148 | id_, params = build_data( |
|
149 | 149 | regular_user_api_key, 'create_repo_group', |
|
150 | 150 | group_name=repo_group_name) |
|
151 | 151 | response = api_call(self.app, params) |
|
152 | 152 | |
|
153 | 153 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
154 | 154 | assert repo_group is not None |
|
155 | 155 | expected = { |
|
156 | 156 | 'msg': 'Created new repo group `%s`' % (repo_group_name,), |
|
157 | 157 | 'repo_group': repo_group.get_api_data() |
|
158 | 158 | } |
|
159 | 159 | try: |
|
160 | 160 | assert_ok(id_, expected, given=response.body) |
|
161 | 161 | finally: |
|
162 | 162 | fixture.destroy_repo_group(repo_group_name) |
|
163 | 163 | |
|
164 | 164 | def test_api_create_repo_group_regular_user_with_admin_perms_to_parent( |
|
165 | 165 | self, user_util): |
|
166 | 166 | |
|
167 | 167 | repo_group_name = 'api-repo-group-parent' |
|
168 | 168 | |
|
169 | 169 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
170 | 170 | assert repo_group is None |
|
171 | 171 | # create the parent |
|
172 | 172 | fixture.create_repo_group(repo_group_name) |
|
173 | 173 | |
|
174 | 174 | # user perms |
|
175 | 175 | regular_user = user_util.create_user() |
|
176 | 176 | regular_user_api_key = regular_user.api_key |
|
177 | 177 | |
|
178 | 178 | usr = UserModel().get_by_username(regular_user.username) |
|
179 | 179 | usr.inherit_default_permissions = False |
|
180 | 180 | Session().add(usr) |
|
181 | 181 | |
|
182 | 182 | RepoGroupModel().grant_user_permission( |
|
183 | 183 | repo_group_name, regular_user.username, 'group.admin') |
|
184 | 184 | Session().commit() |
|
185 | 185 | |
|
186 | 186 | full_repo_group_name = repo_group_name + '/' + repo_group_name |
|
187 | 187 | id_, params = build_data( |
|
188 | 188 | regular_user_api_key, 'create_repo_group', |
|
189 | 189 | group_name=full_repo_group_name) |
|
190 | 190 | response = api_call(self.app, params) |
|
191 | 191 | |
|
192 | 192 | repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name) |
|
193 | 193 | assert repo_group is not None |
|
194 | 194 | expected = { |
|
195 | 195 | 'msg': 'Created new repo group `{}`'.format(full_repo_group_name), |
|
196 | 196 | 'repo_group': repo_group.get_api_data() |
|
197 | 197 | } |
|
198 | 198 | try: |
|
199 | 199 | assert_ok(id_, expected, given=response.body) |
|
200 | 200 | finally: |
|
201 | 201 | fixture.destroy_repo_group(full_repo_group_name) |
|
202 | 202 | fixture.destroy_repo_group(repo_group_name) |
|
203 | 203 | |
|
204 | 204 | def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self): |
|
205 | 205 | repo_group_name = 'api-repo-group' |
|
206 | 206 | |
|
207 | 207 | id_, params = build_data( |
|
208 | 208 | self.apikey_regular, 'create_repo_group', |
|
209 | 209 | group_name=repo_group_name) |
|
210 | 210 | response = api_call(self.app, params) |
|
211 | 211 | |
|
212 | 212 | expected = { |
|
213 | 213 | 'repo_group': |
|
214 | 214 | u'You do not have the permission to store ' |
|
215 | 215 | u'repository groups in the root location.'} |
|
216 | 216 | assert_error(id_, expected, given=response.body) |
|
217 | 217 | |
|
218 | 218 | def test_api_create_repo_group_regular_user_no_parent_group_perms(self): |
|
219 | 219 | repo_group_name = 'api-repo-group-regular-user' |
|
220 | 220 | |
|
221 | 221 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
222 | 222 | assert repo_group is None |
|
223 | 223 | # create the parent |
|
224 | 224 | fixture.create_repo_group(repo_group_name) |
|
225 | 225 | |
|
226 | 226 | full_repo_group_name = repo_group_name+'/'+repo_group_name |
|
227 | 227 | |
|
228 | 228 | id_, params = build_data( |
|
229 | 229 | self.apikey_regular, 'create_repo_group', |
|
230 | 230 | group_name=full_repo_group_name) |
|
231 | 231 | response = api_call(self.app, params) |
|
232 | 232 | |
|
233 | 233 | expected = { |
|
234 | 234 | 'repo_group': |
|
235 | 235 | u"You do not have the permissions to store " |
|
236 | 236 | u"repository groups inside repository group `{}`".format(repo_group_name)} |
|
237 | 237 | try: |
|
238 | 238 | assert_error(id_, expected, given=response.body) |
|
239 | 239 | finally: |
|
240 | 240 | fixture.destroy_repo_group(repo_group_name) |
|
241 | 241 | |
|
242 | 242 | def test_api_create_repo_group_regular_user_no_permission_to_specify_owner( |
|
243 | 243 | self): |
|
244 | 244 | repo_group_name = 'api-repo-group' |
|
245 | 245 | |
|
246 | 246 | id_, params = build_data( |
|
247 | 247 | self.apikey_regular, 'create_repo_group', |
|
248 | 248 | group_name=repo_group_name, |
|
249 | 249 | owner=TEST_USER_ADMIN_LOGIN,) |
|
250 | 250 | response = api_call(self.app, params) |
|
251 | 251 | |
|
252 | 252 | expected = "Only RhodeCode super-admin can specify `owner` param" |
|
253 | 253 | assert_error(id_, expected, given=response.body) |
|
254 | 254 | |
|
255 | 255 | @mock.patch.object(RepoGroupModel, 'create', crash) |
|
256 | 256 | def test_api_create_repo_group_exception_occurred(self): |
|
257 | 257 | repo_group_name = 'api-repo-group' |
|
258 | 258 | |
|
259 | 259 | repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name) |
|
260 | 260 | assert repo_group is None |
|
261 | 261 | |
|
262 | 262 | id_, params = build_data( |
|
263 | 263 | self.apikey, 'create_repo_group', |
|
264 | 264 | group_name=repo_group_name, |
|
265 | 265 | owner=TEST_USER_ADMIN_LOGIN,) |
|
266 | 266 | response = api_call(self.app, params) |
|
267 | 267 | expected = 'failed to create repo group `%s`' % (repo_group_name,) |
|
268 | 268 | assert_error(id_, expected, given=response.body) |
|
269 | 269 | |
|
270 | 270 | def test_create_group_with_extra_slashes_in_name(self, user_util): |
|
271 | 271 | existing_repo_group = user_util.create_repo_group() |
|
272 | 272 | dirty_group_name = '//{}//group2//'.format( |
|
273 | 273 | existing_repo_group.group_name) |
|
274 | 274 | cleaned_group_name = '{}/group2'.format( |
|
275 | 275 | existing_repo_group.group_name) |
|
276 | 276 | |
|
277 | 277 | id_, params = build_data( |
|
278 | 278 | self.apikey, 'create_repo_group', |
|
279 | 279 | group_name=dirty_group_name, |
|
280 | 280 | owner=TEST_USER_ADMIN_LOGIN,) |
|
281 | 281 | response = api_call(self.app, params) |
|
282 | 282 | repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name) |
|
283 | 283 | expected = { |
|
284 | 284 | 'msg': 'Created new repo group `%s`' % (cleaned_group_name,), |
|
285 | 285 | 'repo_group': repo_group.get_api_data() |
|
286 | 286 | } |
|
287 | 287 | assert_ok(id_, expected, given=response.body) |
|
288 | 288 | fixture.destroy_repo_group(cleaned_group_name) |
@@ -1,206 +1,206 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.lib.auth import check_password |
|
24 | 24 | from rhodecode.model.user import UserModel |
|
25 | 25 | from rhodecode.tests import ( |
|
26 | 26 | TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL) |
|
27 | 27 | from rhodecode.api.tests.utils import ( |
|
28 | 28 | build_data, api_call, assert_ok, assert_error, jsonify, crash) |
|
29 | 29 | from rhodecode.tests.fixture import Fixture |
|
30 | 30 | from rhodecode.model.db import RepoGroup |
|
31 | 31 | |
|
32 | 32 | |
|
33 | 33 | # TODO: mikhail: remove fixture from here |
|
34 | 34 | fixture = Fixture() |
|
35 | 35 | |
|
36 | 36 | |
|
37 | 37 | @pytest.mark.usefixtures("testuser_api", "app") |
|
38 | 38 | class TestCreateUser(object): |
|
39 | 39 | def test_api_create_existing_user(self): |
|
40 | 40 | id_, params = build_data( |
|
41 | 41 | self.apikey, 'create_user', |
|
42 | 42 | username=TEST_USER_ADMIN_LOGIN, |
|
43 | 43 | email='test@foo.com', |
|
44 | 44 | password='trololo') |
|
45 | 45 | response = api_call(self.app, params) |
|
46 | 46 | |
|
47 | 47 | expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,) |
|
48 | 48 | assert_error(id_, expected, given=response.body) |
|
49 | 49 | |
|
50 | 50 | def test_api_create_user_with_existing_email(self): |
|
51 | 51 | id_, params = build_data( |
|
52 | 52 | self.apikey, 'create_user', |
|
53 | 53 | username=TEST_USER_ADMIN_LOGIN + 'new', |
|
54 | 54 | email=TEST_USER_REGULAR_EMAIL, |
|
55 | 55 | password='trololo') |
|
56 | 56 | response = api_call(self.app, params) |
|
57 | 57 | |
|
58 | 58 | expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,) |
|
59 | 59 | assert_error(id_, expected, given=response.body) |
|
60 | 60 | |
|
61 | 61 | def test_api_create_user_with_wrong_username(self): |
|
62 | 62 | bad_username = '<> HELLO WORLD <>' |
|
63 | 63 | id_, params = build_data( |
|
64 | 64 | self.apikey, 'create_user', |
|
65 | 65 | username=bad_username, |
|
66 | 66 | email='new@email.com', |
|
67 | 67 | password='trololo') |
|
68 | 68 | response = api_call(self.app, params) |
|
69 | 69 | |
|
70 | 70 | expected = {'username': |
|
71 | 71 | "Username may only contain alphanumeric characters " |
|
72 | 72 | "underscores, periods or dashes and must begin with " |
|
73 | 73 | "alphanumeric character or underscore"} |
|
74 | 74 | assert_error(id_, expected, given=response.body) |
|
75 | 75 | |
|
76 | 76 | def test_api_create_user(self): |
|
77 | 77 | username = 'test_new_api_user' |
|
78 | 78 | email = username + "@foo.com" |
|
79 | 79 | |
|
80 | 80 | id_, params = build_data( |
|
81 | 81 | self.apikey, 'create_user', |
|
82 | 82 | username=username, |
|
83 | 83 | email=email, |
|
84 | 84 | description='CTO of Things', |
|
85 | 85 | password='example') |
|
86 | 86 | response = api_call(self.app, params) |
|
87 | 87 | |
|
88 | 88 | usr = UserModel().get_by_username(username) |
|
89 | 89 | ret = { |
|
90 | 90 | 'msg': 'created new user `%s`' % (username,), |
|
91 | 91 | 'user': jsonify(usr.get_api_data(include_secrets=True)), |
|
92 | 92 | } |
|
93 | 93 | try: |
|
94 | 94 | expected = ret |
|
95 | 95 | assert check_password('example', usr.password) |
|
96 | 96 | assert_ok(id_, expected, given=response.body) |
|
97 | 97 | finally: |
|
98 | 98 | fixture.destroy_user(usr.user_id) |
|
99 | 99 | |
|
100 | 100 | def test_api_create_user_without_password(self): |
|
101 | 101 | username = 'test_new_api_user_passwordless' |
|
102 | 102 | email = username + "@foo.com" |
|
103 | 103 | |
|
104 | 104 | id_, params = build_data( |
|
105 | 105 | self.apikey, 'create_user', |
|
106 | 106 | username=username, |
|
107 | 107 | email=email) |
|
108 | 108 | response = api_call(self.app, params) |
|
109 | 109 | |
|
110 | 110 | usr = UserModel().get_by_username(username) |
|
111 | 111 | ret = { |
|
112 | 112 | 'msg': 'created new user `%s`' % (username,), |
|
113 | 113 | 'user': jsonify(usr.get_api_data(include_secrets=True)), |
|
114 | 114 | } |
|
115 | 115 | try: |
|
116 | 116 | expected = ret |
|
117 | 117 | assert_ok(id_, expected, given=response.body) |
|
118 | 118 | finally: |
|
119 | 119 | fixture.destroy_user(usr.user_id) |
|
120 | 120 | |
|
121 | 121 | def test_api_create_user_with_extern_name(self): |
|
122 | 122 | username = 'test_new_api_user_passwordless' |
|
123 | 123 | email = username + "@foo.com" |
|
124 | 124 | |
|
125 | 125 | id_, params = build_data( |
|
126 | 126 | self.apikey, 'create_user', |
|
127 | 127 | username=username, |
|
128 | 128 | email=email, extern_name='rhodecode') |
|
129 | 129 | response = api_call(self.app, params) |
|
130 | 130 | |
|
131 | 131 | usr = UserModel().get_by_username(username) |
|
132 | 132 | ret = { |
|
133 | 133 | 'msg': 'created new user `%s`' % (username,), |
|
134 | 134 | 'user': jsonify(usr.get_api_data(include_secrets=True)), |
|
135 | 135 | } |
|
136 | 136 | try: |
|
137 | 137 | expected = ret |
|
138 | 138 | assert_ok(id_, expected, given=response.body) |
|
139 | 139 | finally: |
|
140 | 140 | fixture.destroy_user(usr.user_id) |
|
141 | 141 | |
|
142 | 142 | def test_api_create_user_with_password_change(self): |
|
143 | 143 | username = 'test_new_api_user_password_change' |
|
144 | 144 | email = username + "@foo.com" |
|
145 | 145 | |
|
146 | 146 | id_, params = build_data( |
|
147 | 147 | self.apikey, 'create_user', |
|
148 | 148 | username=username, |
|
149 | 149 | email=email, extern_name='rhodecode', |
|
150 | 150 | force_password_change=True) |
|
151 | 151 | response = api_call(self.app, params) |
|
152 | 152 | |
|
153 | 153 | usr = UserModel().get_by_username(username) |
|
154 | 154 | ret = { |
|
155 | 155 | 'msg': 'created new user `%s`' % (username,), |
|
156 | 156 | 'user': jsonify(usr.get_api_data(include_secrets=True)), |
|
157 | 157 | } |
|
158 | 158 | try: |
|
159 | 159 | expected = ret |
|
160 | 160 | assert_ok(id_, expected, given=response.body) |
|
161 | 161 | finally: |
|
162 | 162 | fixture.destroy_user(usr.user_id) |
|
163 | 163 | |
|
164 | 164 | def test_api_create_user_with_personal_repo_group(self): |
|
165 | 165 | username = 'test_new_api_user_personal_group' |
|
166 | 166 | email = username + "@foo.com" |
|
167 | 167 | |
|
168 | 168 | id_, params = build_data( |
|
169 | 169 | self.apikey, 'create_user', |
|
170 | 170 | username=username, |
|
171 | 171 | email=email, extern_name='rhodecode', |
|
172 | 172 | create_personal_repo_group=True) |
|
173 | 173 | response = api_call(self.app, params) |
|
174 | 174 | |
|
175 | 175 | usr = UserModel().get_by_username(username) |
|
176 | 176 | ret = { |
|
177 | 177 | 'msg': 'created new user `%s`' % (username,), |
|
178 | 178 | 'user': jsonify(usr.get_api_data(include_secrets=True)), |
|
179 | 179 | } |
|
180 | 180 | |
|
181 | 181 | personal_group = RepoGroup.get_by_group_name(username) |
|
182 | 182 | assert personal_group |
|
183 | 183 | assert personal_group.personal == True |
|
184 | 184 | assert personal_group.user.username == username |
|
185 | 185 | |
|
186 | 186 | try: |
|
187 | 187 | expected = ret |
|
188 | 188 | assert_ok(id_, expected, given=response.body) |
|
189 | 189 | finally: |
|
190 | 190 | fixture.destroy_repo_group(username) |
|
191 | 191 | fixture.destroy_user(usr.user_id) |
|
192 | 192 | |
|
193 | 193 | @mock.patch.object(UserModel, 'create_or_update', crash) |
|
194 | 194 | def test_api_create_user_when_exception_happened(self): |
|
195 | 195 | |
|
196 | 196 | username = 'test_new_api_user' |
|
197 | 197 | email = username + "@foo.com" |
|
198 | 198 | |
|
199 | 199 | id_, params = build_data( |
|
200 | 200 | self.apikey, 'create_user', |
|
201 | 201 | username=username, |
|
202 | 202 | email=email, |
|
203 | 203 | password='trololo') |
|
204 | 204 | response = api_call(self.app, params) |
|
205 | 205 | expected = 'failed to create user `%s`' % (username,) |
|
206 | 206 | assert_error(id_, expected, given=response.body) |
@@ -1,126 +1,126 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.meta import Session |
|
24 | 24 | from rhodecode.model.user import UserModel |
|
25 | 25 | from rhodecode.model.user_group import UserGroupModel |
|
26 | 26 | from rhodecode.api.tests.utils import ( |
|
27 | 27 | build_data, api_call, assert_error, assert_ok, crash, jsonify) |
|
28 | 28 | from rhodecode.tests.fixture import Fixture |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | @pytest.mark.usefixtures("testuser_api", "app") |
|
32 | 32 | class TestCreateUserGroup(object): |
|
33 | 33 | fixture = Fixture() |
|
34 | 34 | |
|
35 | 35 | def test_api_create_user_group(self): |
|
36 | 36 | group_name = 'some_new_group' |
|
37 | 37 | id_, params = build_data( |
|
38 | 38 | self.apikey, 'create_user_group', group_name=group_name) |
|
39 | 39 | response = api_call(self.app, params) |
|
40 | 40 | |
|
41 | 41 | ret = { |
|
42 | 42 | 'msg': 'created new user group `%s`' % (group_name,), |
|
43 | 43 | 'user_group': jsonify( |
|
44 | 44 | UserGroupModel() |
|
45 | 45 | .get_by_name(group_name) |
|
46 | 46 | .get_api_data() |
|
47 | 47 | ) |
|
48 | 48 | } |
|
49 | 49 | expected = ret |
|
50 | 50 | assert_ok(id_, expected, given=response.body) |
|
51 | 51 | self.fixture.destroy_user_group(group_name) |
|
52 | 52 | |
|
53 | 53 | def test_api_create_user_group_regular_user(self): |
|
54 | 54 | group_name = 'some_new_group' |
|
55 | 55 | |
|
56 | 56 | usr = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
57 | 57 | usr.inherit_default_permissions = False |
|
58 | 58 | Session().add(usr) |
|
59 | 59 | UserModel().grant_perm( |
|
60 | 60 | self.TEST_USER_LOGIN, 'hg.usergroup.create.true') |
|
61 | 61 | Session().commit() |
|
62 | 62 | |
|
63 | 63 | id_, params = build_data( |
|
64 | 64 | self.apikey_regular, 'create_user_group', group_name=group_name) |
|
65 | 65 | response = api_call(self.app, params) |
|
66 | 66 | |
|
67 | 67 | expected = { |
|
68 | 68 | 'msg': 'created new user group `%s`' % (group_name,), |
|
69 | 69 | 'user_group': jsonify( |
|
70 | 70 | UserGroupModel() |
|
71 | 71 | .get_by_name(group_name) |
|
72 | 72 | .get_api_data() |
|
73 | 73 | ) |
|
74 | 74 | } |
|
75 | 75 | try: |
|
76 | 76 | assert_ok(id_, expected, given=response.body) |
|
77 | 77 | finally: |
|
78 | 78 | self.fixture.destroy_user_group(group_name) |
|
79 | 79 | UserModel().revoke_perm( |
|
80 | 80 | self.TEST_USER_LOGIN, 'hg.usergroup.create.true') |
|
81 | 81 | usr = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
82 | 82 | usr.inherit_default_permissions = True |
|
83 | 83 | Session().add(usr) |
|
84 | 84 | Session().commit() |
|
85 | 85 | |
|
86 | 86 | def test_api_create_user_group_regular_user_no_permission(self): |
|
87 | 87 | group_name = 'some_new_group' |
|
88 | 88 | id_, params = build_data( |
|
89 | 89 | self.apikey_regular, 'create_user_group', group_name=group_name) |
|
90 | 90 | response = api_call(self.app, params) |
|
91 | 91 | expected = "Access was denied to this resource." |
|
92 | 92 | assert_error(id_, expected, given=response.body) |
|
93 | 93 | |
|
94 | 94 | def test_api_create_user_group_that_exist(self, user_util): |
|
95 | 95 | group = user_util.create_user_group() |
|
96 | 96 | group_name = group.users_group_name |
|
97 | 97 | |
|
98 | 98 | id_, params = build_data( |
|
99 | 99 | self.apikey, 'create_user_group', group_name=group_name) |
|
100 | 100 | response = api_call(self.app, params) |
|
101 | 101 | |
|
102 | 102 | expected = "user group `%s` already exist" % (group_name,) |
|
103 | 103 | assert_error(id_, expected, given=response.body) |
|
104 | 104 | |
|
105 | 105 | @mock.patch.object(UserGroupModel, 'create', crash) |
|
106 | 106 | def test_api_create_user_group_exception_occurred(self): |
|
107 | 107 | group_name = 'exception_happens' |
|
108 | 108 | id_, params = build_data( |
|
109 | 109 | self.apikey, 'create_user_group', group_name=group_name) |
|
110 | 110 | response = api_call(self.app, params) |
|
111 | 111 | |
|
112 | 112 | expected = 'failed to create group `%s`' % (group_name,) |
|
113 | 113 | assert_error(id_, expected, given=response.body) |
|
114 | 114 | |
|
115 | 115 | def test_api_create_user_group_with_wrong_name(self, user_util): |
|
116 | 116 | |
|
117 | 117 | group_name = 'wrong NAME <>' |
|
118 | 118 | id_, params = build_data( |
|
119 | 119 | self.apikey, 'create_user_group', group_name=group_name) |
|
120 | 120 | response = api_call(self.app, params) |
|
121 | 121 | |
|
122 | 122 | expected = {"user_group_name": |
|
123 | 123 | "Allowed in name are letters, numbers, and `-`, `_`, " |
|
124 | 124 | "`.` Name must start with a letter or number. " |
|
125 | 125 | "Got `{}`".format(group_name)} |
|
126 | 126 | assert_error(id_, expected, given=response.body) |
@@ -1,60 +1,60 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.gist import GistModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestApiDeleteGist(object): |
|
30 | 30 | def test_api_delete_gist(self, gist_util): |
|
31 | 31 | gist_id = gist_util.create_gist().gist_access_id |
|
32 | 32 | id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id) |
|
33 | 33 | response = api_call(self.app, params) |
|
34 | 34 | expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)} |
|
35 | 35 | assert_ok(id_, expected, given=response.body) |
|
36 | 36 | |
|
37 | 37 | def test_api_delete_gist_regular_user(self, gist_util): |
|
38 | 38 | gist_id = gist_util.create_gist( |
|
39 | 39 | owner=self.TEST_USER_LOGIN).gist_access_id |
|
40 | 40 | id_, params = build_data( |
|
41 | 41 | self.apikey_regular, 'delete_gist', gistid=gist_id) |
|
42 | 42 | response = api_call(self.app, params) |
|
43 | 43 | expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)} |
|
44 | 44 | assert_ok(id_, expected, given=response.body) |
|
45 | 45 | |
|
46 | 46 | def test_api_delete_gist_regular_user_no_permission(self, gist_util): |
|
47 | 47 | gist_id = gist_util.create_gist().gist_access_id |
|
48 | 48 | id_, params = build_data( |
|
49 | 49 | self.apikey_regular, 'delete_gist', gistid=gist_id) |
|
50 | 50 | response = api_call(self.app, params) |
|
51 | 51 | expected = 'gist `%s` does not exist' % (gist_id,) |
|
52 | 52 | assert_error(id_, expected, given=response.body) |
|
53 | 53 | |
|
54 | 54 | @mock.patch.object(GistModel, 'delete', crash) |
|
55 | 55 | def test_api_delete_gist_exception_occurred(self, gist_util): |
|
56 | 56 | gist_id = gist_util.create_gist().gist_access_id |
|
57 | 57 | id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id) |
|
58 | 58 | response = api_call(self.app, params) |
|
59 | 59 | expected = 'failed to delete gist ID:%s' % (gist_id,) |
|
60 | 60 | assert_error(id_, expected, given=response.body) |
@@ -1,73 +1,73 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo import RepoModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestApiDeleteRepo(object): |
|
30 | 30 | def test_api_delete_repo(self, backend): |
|
31 | 31 | repo = backend.create_repo() |
|
32 | 32 | repo_name = repo.repo_name |
|
33 | 33 | id_, params = build_data( |
|
34 | 34 | self.apikey, 'delete_repo', repoid=repo.repo_name, ) |
|
35 | 35 | response = api_call(self.app, params) |
|
36 | 36 | |
|
37 | 37 | expected = { |
|
38 | 38 | 'msg': 'Deleted repository `%s`' % (repo_name,), |
|
39 | 39 | 'success': True |
|
40 | 40 | } |
|
41 | 41 | assert_ok(id_, expected, given=response.body) |
|
42 | 42 | |
|
43 | 43 | def test_api_delete_repo_by_non_admin(self, backend, user_regular): |
|
44 | 44 | repo = backend.create_repo(cur_user=user_regular.username) |
|
45 | 45 | repo_name = repo.repo_name |
|
46 | 46 | id_, params = build_data( |
|
47 | 47 | user_regular.api_key, 'delete_repo', repoid=repo.repo_name, ) |
|
48 | 48 | response = api_call(self.app, params) |
|
49 | 49 | |
|
50 | 50 | expected = { |
|
51 | 51 | 'msg': 'Deleted repository `%s`' % (repo_name,), |
|
52 | 52 | 'success': True |
|
53 | 53 | } |
|
54 | 54 | assert_ok(id_, expected, given=response.body) |
|
55 | 55 | |
|
56 | 56 | def test_api_delete_repo_by_non_admin_no_permission(self, backend): |
|
57 | 57 | repo = backend.create_repo() |
|
58 | 58 | repo_name = repo.repo_name |
|
59 | 59 | id_, params = build_data( |
|
60 | 60 | self.apikey_regular, 'delete_repo', repoid=repo.repo_name, ) |
|
61 | 61 | response = api_call(self.app, params) |
|
62 | 62 | expected = 'repository `%s` does not exist' % (repo_name) |
|
63 | 63 | assert_error(id_, expected, given=response.body) |
|
64 | 64 | |
|
65 | 65 | def test_api_delete_repo_exception_occurred(self, backend): |
|
66 | 66 | repo = backend.create_repo() |
|
67 | 67 | repo_name = repo.repo_name |
|
68 | 68 | id_, params = build_data( |
|
69 | 69 | self.apikey, 'delete_repo', repoid=repo.repo_name, ) |
|
70 | 70 | with mock.patch.object(RepoModel, 'delete', crash): |
|
71 | 71 | response = api_call(self.app, params) |
|
72 | 72 | expected = 'failed to delete repository `%s`' % (repo_name,) |
|
73 | 73 | assert_error(id_, expected, given=response.body) |
@@ -1,85 +1,85 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo_group import RepoGroupModel |
|
24 | 24 | from rhodecode.model.user import UserModel |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestApiDeleteRepoGroup(object): |
|
31 | 31 | def test_api_delete_repo_group(self, user_util): |
|
32 | 32 | repo_group = user_util.create_repo_group(auto_cleanup=False) |
|
33 | 33 | repo_group_name = repo_group.group_name |
|
34 | 34 | repo_group_id = repo_group.group_id |
|
35 | 35 | id_, params = build_data( |
|
36 | 36 | self.apikey, 'delete_repo_group', repogroupid=repo_group_name, ) |
|
37 | 37 | response = api_call(self.app, params) |
|
38 | 38 | |
|
39 | 39 | ret = { |
|
40 | 40 | 'msg': 'deleted repo group ID:%s %s' % ( |
|
41 | 41 | repo_group_id, repo_group_name |
|
42 | 42 | ), |
|
43 | 43 | 'repo_group': None |
|
44 | 44 | } |
|
45 | 45 | expected = ret |
|
46 | 46 | assert_ok(id_, expected, given=response.body) |
|
47 | 47 | gr = RepoGroupModel()._get_repo_group(repo_group_name) |
|
48 | 48 | assert gr is None |
|
49 | 49 | |
|
50 | 50 | def test_api_delete_repo_group_regular_user(self, user_util): |
|
51 | 51 | repo_group = user_util.create_repo_group(auto_cleanup=False) |
|
52 | 52 | repo_group_name = repo_group.group_name |
|
53 | 53 | repo_group_id = repo_group.group_id |
|
54 | 54 | |
|
55 | 55 | user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
56 | 56 | user_util.grant_user_permission_to_repo_group( |
|
57 | 57 | repo_group, user, 'group.admin') |
|
58 | 58 | |
|
59 | 59 | id_, params = build_data( |
|
60 | 60 | self.apikey, 'delete_repo_group', repogroupid=repo_group_name, ) |
|
61 | 61 | response = api_call(self.app, params) |
|
62 | 62 | |
|
63 | 63 | ret = { |
|
64 | 64 | 'msg': 'deleted repo group ID:%s %s' % ( |
|
65 | 65 | repo_group_id, repo_group_name |
|
66 | 66 | ), |
|
67 | 67 | 'repo_group': None |
|
68 | 68 | } |
|
69 | 69 | expected = ret |
|
70 | 70 | assert_ok(id_, expected, given=response.body) |
|
71 | 71 | gr = RepoGroupModel()._get_repo_group(repo_group_name) |
|
72 | 72 | assert gr is None |
|
73 | 73 | |
|
74 | 74 | def test_api_delete_repo_group_regular_user_no_permission(self, user_util): |
|
75 | 75 | repo_group = user_util.create_repo_group() |
|
76 | 76 | repo_group_name = repo_group.group_name |
|
77 | 77 | |
|
78 | 78 | id_, params = build_data( |
|
79 | 79 | self.apikey_regular, 'delete_repo_group', |
|
80 | 80 | repogroupid=repo_group_name, ) |
|
81 | 81 | response = api_call(self.app, params) |
|
82 | 82 | |
|
83 | 83 | expected = 'repository group `%s` does not exist' % ( |
|
84 | 84 | repo_group_name,) |
|
85 | 85 | assert_error(id_, expected, given=response.body) |
@@ -1,56 +1,56 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import mock |
|
22 | 22 | import pytest |
|
23 | 23 | |
|
24 | 24 | from rhodecode.model.user import UserModel |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_ok, assert_error, crash) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestDeleteUser(object): |
|
31 | 31 | def test_api_delete_user(self, user_util): |
|
32 | 32 | usr = user_util.create_user(auto_cleanup=False) |
|
33 | 33 | |
|
34 | 34 | username = usr.username |
|
35 | 35 | usr_id = usr.user_id |
|
36 | 36 | |
|
37 | 37 | id_, params = build_data(self.apikey, 'delete_user', userid=username) |
|
38 | 38 | response = api_call(self.app, params) |
|
39 | 39 | |
|
40 | 40 | ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username), |
|
41 | 41 | 'user': None} |
|
42 | 42 | expected = ret |
|
43 | 43 | assert_ok(id_, expected, given=response.body) |
|
44 | 44 | |
|
45 | 45 | @mock.patch.object(UserModel, 'delete', crash) |
|
46 | 46 | def test_api_delete_user_when_exception_happened(self, user_util): |
|
47 | 47 | usr = user_util.create_user() |
|
48 | 48 | username = usr.username |
|
49 | 49 | |
|
50 | 50 | id_, params = build_data( |
|
51 | 51 | self.apikey, 'delete_user', userid=username, ) |
|
52 | 52 | response = api_call(self.app, params) |
|
53 | 53 | ret = 'failed to delete user ID:%s %s' % (usr.user_id, |
|
54 | 54 | usr.username) |
|
55 | 55 | expected = ret |
|
56 | 56 | assert_error(id_, expected, given=response.body) |
@@ -1,105 +1,105 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import mock |
|
22 | 22 | import pytest |
|
23 | 23 | |
|
24 | 24 | from rhodecode.model.user import UserModel |
|
25 | 25 | from rhodecode.model.user_group import UserGroupModel |
|
26 | 26 | from rhodecode.api.tests.utils import ( |
|
27 | 27 | build_data, api_call, assert_error, assert_ok, crash) |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
31 | 31 | class TestDeleteUserGroup(object): |
|
32 | 32 | def test_api_delete_user_group(self, user_util): |
|
33 | 33 | user_group = user_util.create_user_group(auto_cleanup=False) |
|
34 | 34 | group_name = user_group.users_group_name |
|
35 | 35 | group_id = user_group.users_group_id |
|
36 | 36 | id_, params = build_data( |
|
37 | 37 | self.apikey, 'delete_user_group', usergroupid=group_name) |
|
38 | 38 | response = api_call(self.app, params) |
|
39 | 39 | |
|
40 | 40 | expected = { |
|
41 | 41 | 'user_group': None, |
|
42 | 42 | 'msg': 'deleted user group ID:%s %s' % (group_id, group_name) |
|
43 | 43 | } |
|
44 | 44 | assert_ok(id_, expected, given=response.body) |
|
45 | 45 | |
|
46 | 46 | def test_api_delete_user_group_regular_user(self, user_util): |
|
47 | 47 | ugroup = user_util.create_user_group(auto_cleanup=False) |
|
48 | 48 | group_name = ugroup.users_group_name |
|
49 | 49 | group_id = ugroup.users_group_id |
|
50 | 50 | user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
51 | 51 | |
|
52 | 52 | user_util.grant_user_permission_to_user_group( |
|
53 | 53 | ugroup, user, 'usergroup.admin') |
|
54 | 54 | |
|
55 | 55 | id_, params = build_data( |
|
56 | 56 | self.apikey_regular, 'delete_user_group', usergroupid=group_name) |
|
57 | 57 | response = api_call(self.app, params) |
|
58 | 58 | |
|
59 | 59 | expected = { |
|
60 | 60 | 'user_group': None, |
|
61 | 61 | 'msg': 'deleted user group ID:%s %s' % (group_id, group_name) |
|
62 | 62 | } |
|
63 | 63 | assert_ok(id_, expected, given=response.body) |
|
64 | 64 | |
|
65 | 65 | def test_api_delete_user_group_regular_user_no_permission(self, user_util): |
|
66 | 66 | user_group = user_util.create_user_group() |
|
67 | 67 | group_name = user_group.users_group_name |
|
68 | 68 | |
|
69 | 69 | id_, params = build_data( |
|
70 | 70 | self.apikey_regular, 'delete_user_group', usergroupid=group_name) |
|
71 | 71 | response = api_call(self.app, params) |
|
72 | 72 | |
|
73 | 73 | expected = 'user group `%s` does not exist' % (group_name) |
|
74 | 74 | assert_error(id_, expected, given=response.body) |
|
75 | 75 | |
|
76 | 76 | def test_api_delete_user_group_that_is_assigned(self, backend, user_util): |
|
77 | 77 | ugroup = user_util.create_user_group() |
|
78 | 78 | group_name = ugroup.users_group_name |
|
79 | 79 | repo = backend.create_repo() |
|
80 | 80 | |
|
81 | 81 | ugr_to_perm = user_util.grant_user_group_permission_to_repo( |
|
82 | 82 | repo, ugroup, 'repository.write') |
|
83 | 83 | msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository) |
|
84 | 84 | |
|
85 | 85 | id_, params = build_data( |
|
86 | 86 | self.apikey, 'delete_user_group', |
|
87 | 87 | usergroupid=group_name) |
|
88 | 88 | response = api_call(self.app, params) |
|
89 | 89 | |
|
90 | 90 | expected = msg |
|
91 | 91 | assert_error(id_, expected, given=response.body) |
|
92 | 92 | |
|
93 | 93 | def test_api_delete_user_group_exception_occurred(self, user_util): |
|
94 | 94 | ugroup = user_util.create_user_group() |
|
95 | 95 | group_name = ugroup.users_group_name |
|
96 | 96 | group_id = ugroup.users_group_id |
|
97 | 97 | id_, params = build_data( |
|
98 | 98 | self.apikey, 'delete_user_group', |
|
99 | 99 | usergroupid=group_name) |
|
100 | 100 | |
|
101 | 101 | with mock.patch.object(UserGroupModel, 'delete', crash): |
|
102 | 102 | response = api_call(self.app, params) |
|
103 | 103 | expected = 'failed to delete user group ID:%s %s' % ( |
|
104 | 104 | group_id, group_name) |
|
105 | 105 | assert_error(id_, expected, given=response.body) |
@@ -1,78 +1,78 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.api.views import deprecated_api |
|
24 | 24 | from rhodecode.lib.ext_json import json |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestCommitComment(object): |
|
31 | 31 | def test_deprecated_message_in_docstring(self): |
|
32 | 32 | docstring = deprecated_api.changeset_comment.__doc__ |
|
33 | 33 | assert '.. deprecated:: 3.4.0' in docstring |
|
34 | 34 | assert 'Please use method `comment_commit` instead.' in docstring |
|
35 | 35 | |
|
36 | 36 | def test_deprecated_message_in_retvalue(self): |
|
37 | 37 | |
|
38 | 38 | id_, params = build_data( |
|
39 | 39 | self.apikey, 'show_ip') |
|
40 | 40 | response = api_call(self.app, params) |
|
41 | 41 | |
|
42 | 42 | expected = { |
|
43 | 43 | 'id': id_, |
|
44 | 44 | 'error': None, |
|
45 | 45 | 'result': json.loads(response.body)['result'], |
|
46 | 46 | 'DEPRECATION_WARNING': |
|
47 | 47 | 'DEPRECATED METHOD Please use method `get_ip` instead.' |
|
48 | 48 | } |
|
49 | 49 | assert expected == json.loads(response.body) |
|
50 | 50 | |
|
51 | 51 | # def test_calls_comment_commit(self, backend, no_notifications): |
|
52 | 52 | # data = { |
|
53 | 53 | # 'repoid': backend.repo_name, |
|
54 | 54 | # 'status': ChangesetStatus.STATUS_APPROVED, |
|
55 | 55 | # 'message': 'Approved', |
|
56 | 56 | # 'revision': 'tip' |
|
57 | 57 | # } |
|
58 | 58 | # with patch.object(repo_api, 'changeset_commit') as comment_mock: |
|
59 | 59 | # id_, params = build_data(self.apikey, 'comment_commit', **data) |
|
60 | 60 | # api_call(self.app, params) |
|
61 | 61 | # |
|
62 | 62 | # _, call_args = comment_mock.call_args |
|
63 | 63 | # data['commit_id'] = data.pop('revision') |
|
64 | 64 | # for key in data: |
|
65 | 65 | # assert call_args[key] == data[key] |
|
66 | 66 | |
|
67 | 67 | # def test_warning_log_contains_deprecation_message(self): |
|
68 | 68 | # api = self.SampleApi() |
|
69 | 69 | # with patch.object(utils, 'log') as log_mock: |
|
70 | 70 | # api.api_method() |
|
71 | 71 | # |
|
72 | 72 | # assert log_mock.warning.call_count == 1 |
|
73 | 73 | # call_args = log_mock.warning.call_args[0] |
|
74 | 74 | # assert ( |
|
75 | 75 | # call_args[0] == |
|
76 | 76 | # 'DEPRECATED API CALL on function %s, please use `%s` instead') |
|
77 | 77 | # assert call_args[1].__name__ == 'api_method' |
|
78 | 78 | # assert call_args[2] == 'new_method' No newline at end of file |
@@ -1,278 +1,278 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import mock |
|
22 | 22 | import pytest |
|
23 | 23 | |
|
24 | 24 | from rhodecode.model.meta import Session |
|
25 | 25 | from rhodecode.model.repo import RepoModel |
|
26 | 26 | from rhodecode.model.repo_group import RepoGroupModel |
|
27 | 27 | from rhodecode.model.user import UserModel |
|
28 | 28 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
29 | 29 | from rhodecode.api.tests.utils import ( |
|
30 | 30 | build_data, api_call, assert_error, assert_ok, crash) |
|
31 | 31 | from rhodecode.tests.fixture import Fixture |
|
32 | 32 | |
|
33 | 33 | |
|
34 | 34 | fixture = Fixture() |
|
35 | 35 | |
|
36 | 36 | |
|
37 | 37 | @pytest.mark.usefixtures("testuser_api", "app") |
|
38 | 38 | class TestApiForkRepo(object): |
|
39 | 39 | def test_api_fork_repo(self, backend): |
|
40 | 40 | source_name = backend['minimal'].repo_name |
|
41 | 41 | fork_name = backend.new_repo_name() |
|
42 | 42 | |
|
43 | 43 | id_, params = build_data( |
|
44 | 44 | self.apikey, 'fork_repo', |
|
45 | 45 | repoid=source_name, |
|
46 | 46 | fork_name=fork_name, |
|
47 | 47 | owner=TEST_USER_ADMIN_LOGIN) |
|
48 | 48 | response = api_call(self.app, params) |
|
49 | 49 | |
|
50 | 50 | expected = { |
|
51 | 51 | 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name), |
|
52 | 52 | 'success': True, |
|
53 | 53 | 'task': None, |
|
54 | 54 | } |
|
55 | 55 | try: |
|
56 | 56 | assert_ok(id_, expected, given=response.body) |
|
57 | 57 | finally: |
|
58 | 58 | fixture.destroy_repo(fork_name) |
|
59 | 59 | |
|
60 | 60 | def test_api_fork_repo_into_group(self, backend, user_util): |
|
61 | 61 | source_name = backend['minimal'].repo_name |
|
62 | 62 | repo_group = user_util.create_repo_group() |
|
63 | 63 | fork_name = '%s/api-repo-fork' % repo_group.group_name |
|
64 | 64 | id_, params = build_data( |
|
65 | 65 | self.apikey, 'fork_repo', |
|
66 | 66 | repoid=source_name, |
|
67 | 67 | fork_name=fork_name, |
|
68 | 68 | owner=TEST_USER_ADMIN_LOGIN) |
|
69 | 69 | response = api_call(self.app, params) |
|
70 | 70 | |
|
71 | 71 | ret = { |
|
72 | 72 | 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name), |
|
73 | 73 | 'success': True, |
|
74 | 74 | 'task': None, |
|
75 | 75 | } |
|
76 | 76 | expected = ret |
|
77 | 77 | try: |
|
78 | 78 | assert_ok(id_, expected, given=response.body) |
|
79 | 79 | finally: |
|
80 | 80 | fixture.destroy_repo(fork_name) |
|
81 | 81 | |
|
82 | 82 | def test_api_fork_repo_non_admin(self, backend): |
|
83 | 83 | source_name = backend['minimal'].repo_name |
|
84 | 84 | fork_name = backend.new_repo_name() |
|
85 | 85 | |
|
86 | 86 | id_, params = build_data( |
|
87 | 87 | self.apikey_regular, 'fork_repo', |
|
88 | 88 | repoid=source_name, |
|
89 | 89 | fork_name=fork_name) |
|
90 | 90 | response = api_call(self.app, params) |
|
91 | 91 | |
|
92 | 92 | expected = { |
|
93 | 93 | 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name), |
|
94 | 94 | 'success': True, |
|
95 | 95 | 'task': None, |
|
96 | 96 | } |
|
97 | 97 | try: |
|
98 | 98 | assert_ok(id_, expected, given=response.body) |
|
99 | 99 | finally: |
|
100 | 100 | fixture.destroy_repo(fork_name) |
|
101 | 101 | |
|
102 | 102 | def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util): |
|
103 | 103 | source_name = backend['minimal'].repo_name |
|
104 | 104 | repo_group = user_util.create_repo_group() |
|
105 | 105 | repo_group_name = repo_group.group_name |
|
106 | 106 | fork_name = '%s/api-repo-fork' % repo_group_name |
|
107 | 107 | |
|
108 | 108 | id_, params = build_data( |
|
109 | 109 | self.apikey_regular, 'fork_repo', |
|
110 | 110 | repoid=source_name, |
|
111 | 111 | fork_name=fork_name) |
|
112 | 112 | response = api_call(self.app, params) |
|
113 | 113 | |
|
114 | 114 | expected = { |
|
115 | 115 | 'repo_group': 'Repository group `{}` does not exist'.format( |
|
116 | 116 | repo_group_name)} |
|
117 | 117 | try: |
|
118 | 118 | assert_error(id_, expected, given=response.body) |
|
119 | 119 | finally: |
|
120 | 120 | fixture.destroy_repo(fork_name) |
|
121 | 121 | |
|
122 | 122 | def test_api_fork_repo_non_admin_into_group(self, backend, user_util): |
|
123 | 123 | source_name = backend['minimal'].repo_name |
|
124 | 124 | repo_group = user_util.create_repo_group() |
|
125 | 125 | fork_name = '%s/api-repo-fork' % repo_group.group_name |
|
126 | 126 | |
|
127 | 127 | RepoGroupModel().grant_user_permission( |
|
128 | 128 | repo_group, self.TEST_USER_LOGIN, 'group.admin') |
|
129 | 129 | Session().commit() |
|
130 | 130 | |
|
131 | 131 | id_, params = build_data( |
|
132 | 132 | self.apikey_regular, 'fork_repo', |
|
133 | 133 | repoid=source_name, |
|
134 | 134 | fork_name=fork_name) |
|
135 | 135 | response = api_call(self.app, params) |
|
136 | 136 | |
|
137 | 137 | expected = { |
|
138 | 138 | 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name), |
|
139 | 139 | 'success': True, |
|
140 | 140 | 'task': None, |
|
141 | 141 | } |
|
142 | 142 | try: |
|
143 | 143 | assert_ok(id_, expected, given=response.body) |
|
144 | 144 | finally: |
|
145 | 145 | fixture.destroy_repo(fork_name) |
|
146 | 146 | |
|
147 | 147 | def test_api_fork_repo_non_admin_specify_owner(self, backend): |
|
148 | 148 | source_name = backend['minimal'].repo_name |
|
149 | 149 | fork_name = backend.new_repo_name() |
|
150 | 150 | id_, params = build_data( |
|
151 | 151 | self.apikey_regular, 'fork_repo', |
|
152 | 152 | repoid=source_name, |
|
153 | 153 | fork_name=fork_name, |
|
154 | 154 | owner=TEST_USER_ADMIN_LOGIN) |
|
155 | 155 | response = api_call(self.app, params) |
|
156 | 156 | expected = 'Only RhodeCode super-admin can specify `owner` param' |
|
157 | 157 | assert_error(id_, expected, given=response.body) |
|
158 | 158 | |
|
159 | 159 | def test_api_fork_repo_non_admin_no_permission_of_source_repo( |
|
160 | 160 | self, backend): |
|
161 | 161 | source_name = backend['minimal'].repo_name |
|
162 | 162 | RepoModel().grant_user_permission(repo=source_name, |
|
163 | 163 | user=self.TEST_USER_LOGIN, |
|
164 | 164 | perm='repository.none') |
|
165 | 165 | fork_name = backend.new_repo_name() |
|
166 | 166 | id_, params = build_data( |
|
167 | 167 | self.apikey_regular, 'fork_repo', |
|
168 | 168 | repoid=backend.repo_name, |
|
169 | 169 | fork_name=fork_name) |
|
170 | 170 | response = api_call(self.app, params) |
|
171 | 171 | expected = 'repository `%s` does not exist' % (backend.repo_name) |
|
172 | 172 | assert_error(id_, expected, given=response.body) |
|
173 | 173 | |
|
174 | 174 | def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level( |
|
175 | 175 | self, backend, user_util): |
|
176 | 176 | |
|
177 | 177 | regular_user = user_util.create_user() |
|
178 | 178 | regular_user_api_key = regular_user.api_key |
|
179 | 179 | usr = UserModel().get_by_username(regular_user.username) |
|
180 | 180 | usr.inherit_default_permissions = False |
|
181 | 181 | Session().add(usr) |
|
182 | 182 | UserModel().grant_perm(regular_user.username, 'hg.fork.repository') |
|
183 | 183 | |
|
184 | 184 | source_name = backend['minimal'].repo_name |
|
185 | 185 | fork_name = backend.new_repo_name() |
|
186 | 186 | id_, params = build_data( |
|
187 | 187 | regular_user_api_key, 'fork_repo', |
|
188 | 188 | repoid=source_name, |
|
189 | 189 | fork_name=fork_name) |
|
190 | 190 | response = api_call(self.app, params) |
|
191 | 191 | expected = { |
|
192 | 192 | "repo_name": "You do not have the permission to " |
|
193 | 193 | "store repositories in the root location."} |
|
194 | 194 | assert_error(id_, expected, given=response.body) |
|
195 | 195 | |
|
196 | 196 | def test_api_fork_repo_non_admin_no_permission_to_fork( |
|
197 | 197 | self, backend, user_util): |
|
198 | 198 | |
|
199 | 199 | regular_user = user_util.create_user() |
|
200 | 200 | regular_user_api_key = regular_user.api_key |
|
201 | 201 | usr = UserModel().get_by_username(regular_user.username) |
|
202 | 202 | usr.inherit_default_permissions = False |
|
203 | 203 | Session().add(usr) |
|
204 | 204 | |
|
205 | 205 | source_name = backend['minimal'].repo_name |
|
206 | 206 | fork_name = backend.new_repo_name() |
|
207 | 207 | id_, params = build_data( |
|
208 | 208 | regular_user_api_key, 'fork_repo', |
|
209 | 209 | repoid=source_name, |
|
210 | 210 | fork_name=fork_name) |
|
211 | 211 | response = api_call(self.app, params) |
|
212 | 212 | |
|
213 | 213 | expected = "Access was denied to this resource." |
|
214 | 214 | assert_error(id_, expected, given=response.body) |
|
215 | 215 | |
|
216 | 216 | def test_api_fork_repo_unknown_owner(self, backend): |
|
217 | 217 | source_name = backend['minimal'].repo_name |
|
218 | 218 | fork_name = backend.new_repo_name() |
|
219 | 219 | owner = 'i-dont-exist' |
|
220 | 220 | id_, params = build_data( |
|
221 | 221 | self.apikey, 'fork_repo', |
|
222 | 222 | repoid=source_name, |
|
223 | 223 | fork_name=fork_name, |
|
224 | 224 | owner=owner) |
|
225 | 225 | response = api_call(self.app, params) |
|
226 | 226 | expected = 'user `%s` does not exist' % (owner,) |
|
227 | 227 | assert_error(id_, expected, given=response.body) |
|
228 | 228 | |
|
229 | 229 | def test_api_fork_repo_fork_exists(self, backend): |
|
230 | 230 | source_name = backend['minimal'].repo_name |
|
231 | 231 | fork_name = backend.new_repo_name() |
|
232 | 232 | fork_repo = fixture.create_fork(source_name, fork_name) |
|
233 | 233 | |
|
234 | 234 | id_, params = build_data( |
|
235 | 235 | self.apikey, 'fork_repo', |
|
236 | 236 | repoid=source_name, |
|
237 | 237 | fork_name=fork_name, |
|
238 | 238 | owner=TEST_USER_ADMIN_LOGIN) |
|
239 | 239 | response = api_call(self.app, params) |
|
240 | 240 | |
|
241 | 241 | try: |
|
242 | 242 | expected = { |
|
243 | 243 | 'unique_repo_name': 'Repository with name `{}` already exists'.format( |
|
244 | 244 | fork_name)} |
|
245 | 245 | assert_error(id_, expected, given=response.body) |
|
246 | 246 | finally: |
|
247 | 247 | fixture.destroy_repo(fork_repo.repo_name) |
|
248 | 248 | |
|
249 | 249 | def test_api_fork_repo_repo_exists(self, backend): |
|
250 | 250 | source_name = backend['minimal'].repo_name |
|
251 | 251 | fork_name = source_name |
|
252 | 252 | |
|
253 | 253 | id_, params = build_data( |
|
254 | 254 | self.apikey, 'fork_repo', |
|
255 | 255 | repoid=source_name, |
|
256 | 256 | fork_name=fork_name, |
|
257 | 257 | owner=TEST_USER_ADMIN_LOGIN) |
|
258 | 258 | response = api_call(self.app, params) |
|
259 | 259 | |
|
260 | 260 | expected = { |
|
261 | 261 | 'unique_repo_name': 'Repository with name `{}` already exists'.format( |
|
262 | 262 | fork_name)} |
|
263 | 263 | assert_error(id_, expected, given=response.body) |
|
264 | 264 | |
|
265 | 265 | @mock.patch.object(RepoModel, 'create_fork', crash) |
|
266 | 266 | def test_api_fork_repo_exception_occurred(self, backend): |
|
267 | 267 | source_name = backend['minimal'].repo_name |
|
268 | 268 | fork_name = backend.new_repo_name() |
|
269 | 269 | id_, params = build_data( |
|
270 | 270 | self.apikey, 'fork_repo', |
|
271 | 271 | repoid=source_name, |
|
272 | 272 | fork_name=fork_name, |
|
273 | 273 | owner=TEST_USER_ADMIN_LOGIN) |
|
274 | 274 | response = api_call(self.app, params) |
|
275 | 275 | |
|
276 | 276 | expected = 'failed to fork repository `%s` as `%s`' % (source_name, |
|
277 | 277 | fork_name) |
|
278 | 278 | assert_error(id_, expected, given=response.body) |
@@ -1,114 +1,114 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | from rhodecode.tests import HG_REPO |
|
22 | 22 | from rhodecode.api.tests.utils import ( |
|
23 | 23 | build_data, api_call, assert_error, assert_ok) |
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | @pytest.mark.usefixtures("testuser_api", "app") |
|
27 | 27 | class TestApiSearch(object): |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.parametrize("sort_dir", [ |
|
30 | 30 | "asc", |
|
31 | 31 | "desc", |
|
32 | 32 | ]) |
|
33 | 33 | @pytest.mark.parametrize("sort", [ |
|
34 | 34 | "xxx", |
|
35 | 35 | "author_email", |
|
36 | 36 | "date", |
|
37 | 37 | "message", |
|
38 | 38 | ]) |
|
39 | 39 | @pytest.mark.parametrize("query, expected_hits, expected_paths", [ |
|
40 | 40 | ('todo', 23, [ |
|
41 | 41 | 'vcs/backends/hg/inmemory.py', |
|
42 | 42 | 'vcs/tests/test_git.py']), |
|
43 | 43 | ('extension:rst installation', 6, [ |
|
44 | 44 | 'docs/index.rst', |
|
45 | 45 | 'docs/installation.rst']), |
|
46 | 46 | ('def repo', 87, [ |
|
47 | 47 | 'vcs/tests/test_git.py', |
|
48 | 48 | 'vcs/tests/test_changesets.py']), |
|
49 | 49 | ('repository:%s def test' % HG_REPO, 18, [ |
|
50 | 50 | 'vcs/tests/test_git.py', |
|
51 | 51 | 'vcs/tests/test_changesets.py']), |
|
52 | 52 | ('"def main"', 9, [ |
|
53 | 53 | 'vcs/__init__.py', |
|
54 | 54 | 'vcs/tests/__init__.py', |
|
55 | 55 | 'vcs/utils/progressbar.py']), |
|
56 | 56 | ('owner:test_admin', 358, [ |
|
57 | 57 | 'vcs/tests/base.py', |
|
58 | 58 | 'MANIFEST.in', |
|
59 | 59 | 'vcs/utils/termcolors.py', |
|
60 | 60 | 'docs/theme/ADC/static/documentation.png']), |
|
61 | 61 | ('owner:test_admin def main', 72, [ |
|
62 | 62 | 'vcs/__init__.py', |
|
63 | 63 | 'vcs/tests/test_utils_filesize.py', |
|
64 | 64 | 'vcs/tests/test_cli.py']), |
|
65 | 65 | ('owner:michał test', 0, []), |
|
66 | 66 | ]) |
|
67 | 67 | def test_search_content_results(self, sort_dir, sort, query, expected_hits, expected_paths): |
|
68 | 68 | id_, params = build_data( |
|
69 | 69 | self.apikey_regular, 'search', |
|
70 | 70 | search_query=query, |
|
71 | 71 | search_sort='{}:{}'.format(sort_dir, sort), |
|
72 | 72 | search_type='content') |
|
73 | 73 | |
|
74 | 74 | response = api_call(self.app, params) |
|
75 | 75 | json_response = response.json |
|
76 | 76 | |
|
77 | 77 | assert json_response['result']['item_count'] == expected_hits |
|
78 | 78 | paths = [x['f_path'] for x in json_response['result']['results']] |
|
79 | 79 | |
|
80 | 80 | for expected_path in expected_paths: |
|
81 | 81 | assert expected_path in paths |
|
82 | 82 | |
|
83 | 83 | @pytest.mark.parametrize("sort_dir", [ |
|
84 | 84 | "asc", |
|
85 | 85 | "desc", |
|
86 | 86 | ]) |
|
87 | 87 | @pytest.mark.parametrize("sort", [ |
|
88 | 88 | "xxx", |
|
89 | 89 | "date", |
|
90 | 90 | "file", |
|
91 | 91 | "size", |
|
92 | 92 | ]) |
|
93 | 93 | @pytest.mark.parametrize("query, expected_hits, expected_paths", [ |
|
94 | 94 | ('readme.rst', 3, []), |
|
95 | 95 | ('test*', 75, []), |
|
96 | 96 | ('*model*', 1, []), |
|
97 | 97 | ('extension:rst', 48, []), |
|
98 | 98 | ('extension:rst api', 24, []), |
|
99 | 99 | ]) |
|
100 | 100 | def test_search_file_paths(self, sort_dir, sort, query, expected_hits, expected_paths): |
|
101 | 101 | id_, params = build_data( |
|
102 | 102 | self.apikey_regular, 'search', |
|
103 | 103 | search_query=query, |
|
104 | 104 | search_sort='{}:{}'.format(sort_dir, sort), |
|
105 | 105 | search_type='path') |
|
106 | 106 | |
|
107 | 107 | response = api_call(self.app, params) |
|
108 | 108 | json_response = response.json |
|
109 | 109 | |
|
110 | 110 | assert json_response['result']['item_count'] == expected_hits |
|
111 | 111 | paths = [x['f_path'] for x in json_response['result']['results']] |
|
112 | 112 | |
|
113 | 113 | for expected_path in expected_paths: |
|
114 | 114 | assert expected_path in paths |
@@ -1,101 +1,101 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.lib.str_utils import safe_bytes |
|
24 | 24 | from rhodecode.model.db import Gist |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestApiGetGist(object): |
|
31 | 31 | def test_api_get_gist(self, gist_util, http_host_only_stub): |
|
32 | 32 | gist = gist_util.create_gist() |
|
33 | 33 | gist_id = gist.gist_access_id |
|
34 | 34 | gist_created_on = gist.created_on |
|
35 | 35 | gist_modified_at = gist.modified_at |
|
36 | 36 | id_, params = build_data( |
|
37 | 37 | self.apikey, 'get_gist', gistid=gist_id, ) |
|
38 | 38 | response = api_call(self.app, params) |
|
39 | 39 | |
|
40 | 40 | expected = { |
|
41 | 41 | 'access_id': gist_id, |
|
42 | 42 | 'created_on': gist_created_on, |
|
43 | 43 | 'modified_at': gist_modified_at, |
|
44 | 44 | 'description': 'new-gist', |
|
45 | 45 | 'expires': -1.0, |
|
46 | 46 | 'gist_id': int(gist_id), |
|
47 | 47 | 'type': 'public', |
|
48 | 48 | 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,), |
|
49 | 49 | 'acl_level': Gist.ACL_LEVEL_PUBLIC, |
|
50 | 50 | 'content': None, |
|
51 | 51 | } |
|
52 | 52 | |
|
53 | 53 | assert_ok(id_, expected, given=response.body) |
|
54 | 54 | |
|
55 | 55 | def test_api_get_gist_with_content(self, gist_util, http_host_only_stub): |
|
56 | 56 | mapping = { |
|
57 | 57 | b'filename1.txt': {'content': b'hello world'}, |
|
58 | 58 | safe_bytes('filename1ą.txt'): {'content': safe_bytes('hello worldę')} |
|
59 | 59 | } |
|
60 | 60 | gist = gist_util.create_gist(gist_mapping=mapping) |
|
61 | 61 | gist_id = gist.gist_access_id |
|
62 | 62 | gist_created_on = gist.created_on |
|
63 | 63 | gist_modified_at = gist.modified_at |
|
64 | 64 | id_, params = build_data( |
|
65 | 65 | self.apikey, 'get_gist', gistid=gist_id, content=True) |
|
66 | 66 | response = api_call(self.app, params) |
|
67 | 67 | |
|
68 | 68 | expected = { |
|
69 | 69 | 'access_id': gist_id, |
|
70 | 70 | 'created_on': gist_created_on, |
|
71 | 71 | 'modified_at': gist_modified_at, |
|
72 | 72 | 'description': 'new-gist', |
|
73 | 73 | 'expires': -1.0, |
|
74 | 74 | 'gist_id': int(gist_id), |
|
75 | 75 | 'type': 'public', |
|
76 | 76 | 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,), |
|
77 | 77 | 'acl_level': Gist.ACL_LEVEL_PUBLIC, |
|
78 | 78 | 'content': { |
|
79 | 79 | u'filename1.txt': u'hello world', |
|
80 | 80 | u'filename1ą.txt': u'hello worldę' |
|
81 | 81 | }, |
|
82 | 82 | } |
|
83 | 83 | |
|
84 | 84 | assert_ok(id_, expected, given=response.body) |
|
85 | 85 | |
|
86 | 86 | def test_api_get_gist_not_existing(self): |
|
87 | 87 | id_, params = build_data( |
|
88 | 88 | self.apikey_regular, 'get_gist', gistid='12345', ) |
|
89 | 89 | response = api_call(self.app, params) |
|
90 | 90 | expected = 'gist `%s` does not exist' % ('12345',) |
|
91 | 91 | assert_error(id_, expected, given=response.body) |
|
92 | 92 | |
|
93 | 93 | def test_api_get_gist_private_gist_without_permission(self, gist_util): |
|
94 | 94 | gist = gist_util.create_gist() |
|
95 | 95 | gist_id = gist.gist_access_id |
|
96 | 96 | id_, params = build_data( |
|
97 | 97 | self.apikey_regular, 'get_gist', gistid=gist_id, ) |
|
98 | 98 | response = api_call(self.app, params) |
|
99 | 99 | |
|
100 | 100 | expected = 'gist `%s` does not exist' % (gist_id,) |
|
101 | 101 | assert_error(id_, expected, given=response.body) |
@@ -1,73 +1,73 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestApiGetGist(object): |
|
30 | 30 | def test_api_get_gists(self, gist_util): |
|
31 | 31 | gist_util.create_gist() |
|
32 | 32 | gist_util.create_gist() |
|
33 | 33 | |
|
34 | 34 | id_, params = build_data(self.apikey, 'get_gists') |
|
35 | 35 | response = api_call(self.app, params) |
|
36 | 36 | assert len(response.json['result']) == 2 |
|
37 | 37 | |
|
38 | 38 | def test_api_get_gists_regular_user(self, gist_util): |
|
39 | 39 | # by admin |
|
40 | 40 | gist_util.create_gist() |
|
41 | 41 | gist_util.create_gist() |
|
42 | 42 | |
|
43 | 43 | # by reg user |
|
44 | 44 | gist_util.create_gist(owner=self.TEST_USER_LOGIN) |
|
45 | 45 | gist_util.create_gist(owner=self.TEST_USER_LOGIN) |
|
46 | 46 | gist_util.create_gist(owner=self.TEST_USER_LOGIN) |
|
47 | 47 | |
|
48 | 48 | id_, params = build_data(self.apikey_regular, 'get_gists') |
|
49 | 49 | response = api_call(self.app, params) |
|
50 | 50 | assert len(response.json['result']) == 3 |
|
51 | 51 | |
|
52 | 52 | def test_api_get_gists_only_for_regular_user(self, gist_util): |
|
53 | 53 | # by admin |
|
54 | 54 | gist_util.create_gist() |
|
55 | 55 | gist_util.create_gist() |
|
56 | 56 | |
|
57 | 57 | # by reg user |
|
58 | 58 | gist_util.create_gist(owner=self.TEST_USER_LOGIN) |
|
59 | 59 | gist_util.create_gist(owner=self.TEST_USER_LOGIN) |
|
60 | 60 | gist_util.create_gist(owner=self.TEST_USER_LOGIN) |
|
61 | 61 | |
|
62 | 62 | id_, params = build_data( |
|
63 | 63 | self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN) |
|
64 | 64 | response = api_call(self.app, params) |
|
65 | 65 | assert len(response.json['result']) == 3 |
|
66 | 66 | |
|
67 | 67 | def test_api_get_gists_regular_user_with_different_userid(self): |
|
68 | 68 | id_, params = build_data( |
|
69 | 69 | self.apikey_regular, 'get_gists', |
|
70 | 70 | userid=TEST_USER_ADMIN_LOGIN) |
|
71 | 71 | response = api_call(self.app, params) |
|
72 | 72 | expected = 'userid is not the same as your user' |
|
73 | 73 | assert_error(id_, expected, given=response.body) |
@@ -1,35 +1,35 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.api.tests.utils import build_data, api_call, assert_ok |
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | @pytest.mark.usefixtures("testuser_api", "app") |
|
27 | 27 | class TestGetIp(object): |
|
28 | 28 | def test_api_get_ip(self): |
|
29 | 29 | id_, params = build_data(self.apikey, 'get_ip') |
|
30 | 30 | response = api_call(self.app, params) |
|
31 | 31 | expected = { |
|
32 | 32 | 'server_ip_addr': '0.0.0.0', |
|
33 | 33 | 'user_ips': [] |
|
34 | 34 | } |
|
35 | 35 | assert_ok(id_, expected, given=response.body) |
@@ -1,90 +1,90 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.db import Repository, User |
|
24 | 24 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_ok, assert_error) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestGetLocks(object): |
|
31 | 31 | def test_api_get_user_locks_regular_user(self): |
|
32 | 32 | id_, params = build_data(self.apikey_regular, 'get_user_locks') |
|
33 | 33 | response = api_call(self.app, params) |
|
34 | 34 | expected = [] |
|
35 | 35 | assert_ok(id_, expected, given=response.body) |
|
36 | 36 | |
|
37 | 37 | def test_api_get_user_locks_with_userid_regular_user(self): |
|
38 | 38 | id_, params = build_data( |
|
39 | 39 | self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN) |
|
40 | 40 | response = api_call(self.app, params) |
|
41 | 41 | expected = 'userid is not the same as your user' |
|
42 | 42 | assert_error(id_, expected, given=response.body) |
|
43 | 43 | |
|
44 | 44 | def test_api_get_user_locks(self): |
|
45 | 45 | id_, params = build_data(self.apikey, 'get_user_locks') |
|
46 | 46 | response = api_call(self.app, params) |
|
47 | 47 | expected = [] |
|
48 | 48 | assert_ok(id_, expected, given=response.body) |
|
49 | 49 | |
|
50 | 50 | @pytest.mark.parametrize("apikey_attr, expect_secrets", [ |
|
51 | 51 | ('apikey', True), |
|
52 | 52 | ('apikey_regular', False), |
|
53 | 53 | ]) |
|
54 | 54 | def test_api_get_user_locks_with_one_locked_repo( |
|
55 | 55 | self, apikey_attr, expect_secrets, backend): |
|
56 | 56 | |
|
57 | 57 | repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN) |
|
58 | 58 | Repository.lock( |
|
59 | 59 | repo, User.get_by_username(self.TEST_USER_LOGIN).user_id) |
|
60 | 60 | |
|
61 | 61 | apikey = getattr(self, apikey_attr) |
|
62 | 62 | |
|
63 | 63 | id_, params = build_data(apikey, 'get_user_locks') |
|
64 | 64 | if apikey_attr == 'apikey': |
|
65 | 65 | # super-admin should call in specific user |
|
66 | 66 | id_, params = build_data(apikey, 'get_user_locks', |
|
67 | 67 | userid=self.TEST_USER_LOGIN) |
|
68 | 68 | |
|
69 | 69 | response = api_call(self.app, params) |
|
70 | 70 | expected = [repo.get_api_data(include_secrets=expect_secrets)] |
|
71 | 71 | assert_ok(id_, expected, given=response.body) |
|
72 | 72 | |
|
73 | 73 | def test_api_get_user_locks_with_one_locked_repo_for_specific_user( |
|
74 | 74 | self, backend): |
|
75 | 75 | repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN) |
|
76 | 76 | |
|
77 | 77 | Repository.lock(repo, User.get_by_username( |
|
78 | 78 | self.TEST_USER_LOGIN).user_id) |
|
79 | 79 | id_, params = build_data( |
|
80 | 80 | self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN) |
|
81 | 81 | response = api_call(self.app, params) |
|
82 | 82 | expected = [repo.get_api_data(include_secrets=True)] |
|
83 | 83 | assert_ok(id_, expected, given=response.body) |
|
84 | 84 | |
|
85 | 85 | def test_api_get_user_locks_with_userid(self): |
|
86 | 86 | id_, params = build_data( |
|
87 | 87 | self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN) |
|
88 | 88 | response = api_call(self.app, params) |
|
89 | 89 | expected = [] |
|
90 | 90 | assert_ok(id_, expected, given=response.body) |
@@ -1,62 +1,62 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.api.tests.utils import build_data, api_call, assert_ok |
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | @pytest.mark.usefixtures("testuser_api", "app") |
|
27 | 27 | class TestGetMethod(object): |
|
28 | 28 | def test_get_methods_no_matches(self): |
|
29 | 29 | id_, params = build_data(self.apikey, 'get_method', pattern='hello') |
|
30 | 30 | response = api_call(self.app, params) |
|
31 | 31 | |
|
32 | 32 | expected = [] |
|
33 | 33 | assert_ok(id_, expected, given=response.body) |
|
34 | 34 | |
|
35 | 35 | def test_get_methods(self): |
|
36 | 36 | id_, params = build_data(self.apikey, 'get_method', pattern='*comment*') |
|
37 | 37 | response = api_call(self.app, params) |
|
38 | 38 | |
|
39 | 39 | expected = [ |
|
40 | 40 | 'changeset_comment', 'comment_pull_request', 'get_pull_request_comments', |
|
41 | 41 | 'comment_commit', 'edit_comment', 'get_comment', 'get_repo_comments' |
|
42 | 42 | ] |
|
43 | 43 | assert_ok(id_, expected, given=response.body) |
|
44 | 44 | |
|
45 | 45 | def test_get_methods_on_single_match(self): |
|
46 | 46 | id_, params = build_data(self.apikey, 'get_method', |
|
47 | 47 | pattern='*comment_commit*') |
|
48 | 48 | response = api_call(self.app, params) |
|
49 | 49 | |
|
50 | 50 | expected = ['comment_commit', |
|
51 | 51 | {'apiuser': '<RequiredType>', |
|
52 | 52 | 'comment_type': "<Optional:'note'>", |
|
53 | 53 | 'commit_id': '<RequiredType>', |
|
54 | 54 | 'extra_recipients': '<Optional:[]>', |
|
55 | 55 | 'message': '<RequiredType>', |
|
56 | 56 | 'repoid': '<RequiredType>', |
|
57 | 57 | 'request': '<RequiredType>', |
|
58 | 58 | 'resolves_comment_id': '<Optional:None>', |
|
59 | 59 | 'status': '<Optional:None>', |
|
60 | 60 | 'userid': '<Optional:<OptionalAttr:apiuser>>', |
|
61 | 61 | 'send_email': '<Optional:True>'}] |
|
62 | 62 | assert_ok(id_, expected, given=response.body) |
@@ -1,143 +1,143 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | import urlobject |
|
23 | 23 | |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok) |
|
26 | 26 | from rhodecode.lib import helpers as h |
|
27 | 27 | from rhodecode.lib.str_utils import safe_str |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | pytestmark = pytest.mark.backends("git", "hg") |
|
31 | 31 | |
|
32 | 32 | |
|
33 | 33 | @pytest.mark.usefixtures("testuser_api", "app") |
|
34 | 34 | class TestGetPullRequest(object): |
|
35 | 35 | |
|
36 | 36 | def test_api_get_pull_request(self, pr_util, http_host_only_stub): |
|
37 | 37 | from rhodecode.model.pull_request import PullRequestModel |
|
38 | 38 | pull_request = pr_util.create_pull_request(mergeable=True) |
|
39 | 39 | id_, params = build_data( |
|
40 | 40 | self.apikey, 'get_pull_request', |
|
41 | 41 | pullrequestid=pull_request.pull_request_id, merge_state=True) |
|
42 | 42 | |
|
43 | 43 | response = api_call(self.app, params) |
|
44 | 44 | |
|
45 | 45 | assert response.status == '200 OK' |
|
46 | 46 | |
|
47 | 47 | url_obj = urlobject.URLObject( |
|
48 | 48 | h.route_url( |
|
49 | 49 | 'pullrequest_show', |
|
50 | 50 | repo_name=pull_request.target_repo.repo_name, |
|
51 | 51 | pull_request_id=pull_request.pull_request_id)) |
|
52 | 52 | |
|
53 | 53 | pr_url = safe_str( |
|
54 | 54 | url_obj.with_netloc(http_host_only_stub)) |
|
55 | 55 | source_url = safe_str( |
|
56 | 56 | pull_request.source_repo.clone_url().with_netloc(http_host_only_stub)) |
|
57 | 57 | target_url = safe_str( |
|
58 | 58 | pull_request.target_repo.clone_url().with_netloc(http_host_only_stub)) |
|
59 | 59 | shadow_url = safe_str( |
|
60 | 60 | PullRequestModel().get_shadow_clone_url(pull_request)) |
|
61 | 61 | |
|
62 | 62 | expected = { |
|
63 | 63 | 'pull_request_id': pull_request.pull_request_id, |
|
64 | 64 | 'url': pr_url, |
|
65 | 65 | 'title': pull_request.title, |
|
66 | 66 | 'description': pull_request.description, |
|
67 | 67 | 'status': pull_request.status, |
|
68 | 68 | 'state': pull_request.pull_request_state, |
|
69 | 69 | 'created_on': pull_request.created_on, |
|
70 | 70 | 'updated_on': pull_request.updated_on, |
|
71 | 71 | 'commit_ids': pull_request.revisions, |
|
72 | 72 | 'review_status': pull_request.calculated_review_status(), |
|
73 | 73 | 'mergeable': { |
|
74 | 74 | 'status': True, |
|
75 | 75 | 'message': 'This pull request can be automatically merged.', |
|
76 | 76 | }, |
|
77 | 77 | 'source': { |
|
78 | 78 | 'clone_url': source_url, |
|
79 | 79 | 'repository': pull_request.source_repo.repo_name, |
|
80 | 80 | 'reference': { |
|
81 | 81 | 'name': pull_request.source_ref_parts.name, |
|
82 | 82 | 'type': pull_request.source_ref_parts.type, |
|
83 | 83 | 'commit_id': pull_request.source_ref_parts.commit_id, |
|
84 | 84 | }, |
|
85 | 85 | }, |
|
86 | 86 | 'target': { |
|
87 | 87 | 'clone_url': target_url, |
|
88 | 88 | 'repository': pull_request.target_repo.repo_name, |
|
89 | 89 | 'reference': { |
|
90 | 90 | 'name': pull_request.target_ref_parts.name, |
|
91 | 91 | 'type': pull_request.target_ref_parts.type, |
|
92 | 92 | 'commit_id': pull_request.target_ref_parts.commit_id, |
|
93 | 93 | }, |
|
94 | 94 | }, |
|
95 | 95 | 'merge': { |
|
96 | 96 | 'clone_url': shadow_url, |
|
97 | 97 | 'reference': { |
|
98 | 98 | 'name': pull_request.shadow_merge_ref.name, |
|
99 | 99 | 'type': pull_request.shadow_merge_ref.type, |
|
100 | 100 | 'commit_id': pull_request.shadow_merge_ref.commit_id, |
|
101 | 101 | }, |
|
102 | 102 | }, |
|
103 | 103 | 'author': pull_request.author.get_api_data(include_secrets=False, |
|
104 | 104 | details='basic'), |
|
105 | 105 | 'reviewers': [ |
|
106 | 106 | { |
|
107 | 107 | 'user': reviewer.get_api_data(include_secrets=False, |
|
108 | 108 | details='basic'), |
|
109 | 109 | 'reasons': reasons, |
|
110 | 110 | 'review_status': st[0][1].status if st else 'not_reviewed', |
|
111 | 111 | } |
|
112 | 112 | for obj, reviewer, reasons, mandatory, st in |
|
113 | 113 | pull_request.reviewers_statuses() |
|
114 | 114 | ] |
|
115 | 115 | } |
|
116 | 116 | assert_ok(id_, expected, response.body) |
|
117 | 117 | |
|
118 | 118 | def test_api_get_pull_request_repo_error(self, pr_util): |
|
119 | 119 | pull_request = pr_util.create_pull_request() |
|
120 | 120 | id_, params = build_data( |
|
121 | 121 | self.apikey, 'get_pull_request', |
|
122 | 122 | repoid=666, pullrequestid=pull_request.pull_request_id) |
|
123 | 123 | response = api_call(self.app, params) |
|
124 | 124 | |
|
125 | 125 | expected = 'repository `666` does not exist' |
|
126 | 126 | assert_error(id_, expected, given=response.body) |
|
127 | 127 | |
|
128 | 128 | def test_api_get_pull_request_pull_request_error(self): |
|
129 | 129 | id_, params = build_data( |
|
130 | 130 | self.apikey, 'get_pull_request', pullrequestid=666) |
|
131 | 131 | response = api_call(self.app, params) |
|
132 | 132 | |
|
133 | 133 | expected = 'pull request `666` does not exist' |
|
134 | 134 | assert_error(id_, expected, given=response.body) |
|
135 | 135 | |
|
136 | 136 | def test_api_get_pull_request_pull_request_error_just_pr_id(self): |
|
137 | 137 | id_, params = build_data( |
|
138 | 138 | self.apikey, 'get_pull_request', |
|
139 | 139 | pullrequestid=666) |
|
140 | 140 | response = api_call(self.app, params) |
|
141 | 141 | |
|
142 | 142 | expected = 'pull request `666` does not exist' |
|
143 | 143 | assert_error(id_, expected, given=response.body) |
@@ -1,83 +1,83 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.api.tests.utils import ( |
|
24 | 24 | build_data, api_call, assert_error, assert_ok) |
|
25 | 25 | |
|
26 | 26 | pytestmark = pytest.mark.backends("git", "hg") |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestGetPullRequestComments(object): |
|
31 | 31 | |
|
32 | 32 | def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub): |
|
33 | 33 | from rhodecode.model.pull_request import PullRequestModel |
|
34 | 34 | |
|
35 | 35 | pull_request = pr_util.create_pull_request(mergeable=True) |
|
36 | 36 | id_, params = build_data( |
|
37 | 37 | self.apikey, 'get_pull_request_comments', |
|
38 | 38 | pullrequestid=pull_request.pull_request_id) |
|
39 | 39 | |
|
40 | 40 | response = api_call(self.app, params) |
|
41 | 41 | |
|
42 | 42 | assert response.status == '200 OK' |
|
43 | 43 | resp_date = response.json['result'][0]['comment_created_on'] |
|
44 | 44 | resp_comment_id = response.json['result'][0]['comment_id'] |
|
45 | 45 | |
|
46 | 46 | expected = [ |
|
47 | 47 | {'comment_author': {'active': True, |
|
48 | 48 | 'full_name_or_username': 'RhodeCode Admin', |
|
49 | 49 | 'username': 'test_admin'}, |
|
50 | 50 | 'comment_created_on': resp_date, |
|
51 | 51 | 'comment_f_path': None, |
|
52 | 52 | 'comment_id': resp_comment_id, |
|
53 | 53 | 'comment_lineno': None, |
|
54 | 54 | 'comment_status': {'status': 'under_review', |
|
55 | 55 | 'status_lbl': 'Under Review'}, |
|
56 | 56 | 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*', |
|
57 | 57 | 'comment_type': 'note', |
|
58 | 58 | 'comment_resolved_by': None, |
|
59 | 59 | 'pull_request_version': None, |
|
60 | 60 | 'comment_last_version': 0, |
|
61 | 61 | 'comment_commit_id': None, |
|
62 | 62 | 'comment_pull_request_id': pull_request.pull_request_id |
|
63 | 63 | } |
|
64 | 64 | ] |
|
65 | 65 | assert_ok(id_, expected, response.body) |
|
66 | 66 | |
|
67 | 67 | def test_api_get_pull_request_comments_repo_error(self, pr_util): |
|
68 | 68 | pull_request = pr_util.create_pull_request() |
|
69 | 69 | id_, params = build_data( |
|
70 | 70 | self.apikey, 'get_pull_request_comments', |
|
71 | 71 | repoid=666, pullrequestid=pull_request.pull_request_id) |
|
72 | 72 | response = api_call(self.app, params) |
|
73 | 73 | |
|
74 | 74 | expected = 'repository `666` does not exist' |
|
75 | 75 | assert_error(id_, expected, given=response.body) |
|
76 | 76 | |
|
77 | 77 | def test_api_get_pull_request_comments_pull_request_error(self): |
|
78 | 78 | id_, params = build_data( |
|
79 | 79 | self.apikey, 'get_pull_request_comments', pullrequestid=666) |
|
80 | 80 | response = api_call(self.app, params) |
|
81 | 81 | |
|
82 | 82 | expected = 'pull request `666` does not exist' |
|
83 | 83 | assert_error(id_, expected, given=response.body) |
@@ -1,81 +1,81 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.meta import Session |
|
24 | 24 | from rhodecode.model.pull_request import PullRequestModel |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestGetPullRequest(object): |
|
31 | 31 | |
|
32 | 32 | @pytest.mark.backends("git", "hg") |
|
33 | 33 | def test_api_get_pull_requests(self, pr_util): |
|
34 | 34 | pull_request = pr_util.create_pull_request() |
|
35 | 35 | pull_request_2 = PullRequestModel().create( |
|
36 | 36 | created_by=pull_request.author, |
|
37 | 37 | source_repo=pull_request.source_repo, |
|
38 | 38 | source_ref=pull_request.source_ref, |
|
39 | 39 | target_repo=pull_request.target_repo, |
|
40 | 40 | target_ref=pull_request.target_ref, |
|
41 | 41 | revisions=pull_request.revisions, |
|
42 | 42 | reviewers=(), |
|
43 | 43 | observers=(), |
|
44 | 44 | title=pull_request.title, |
|
45 | 45 | description=pull_request.description, |
|
46 | 46 | ) |
|
47 | 47 | Session().commit() |
|
48 | 48 | id_, params = build_data( |
|
49 | 49 | self.apikey, 'get_pull_requests', |
|
50 | 50 | repoid=pull_request.target_repo.repo_name) |
|
51 | 51 | response = api_call(self.app, params) |
|
52 | 52 | assert response.status == '200 OK' |
|
53 | 53 | assert len(response.json['result']) == 2 |
|
54 | 54 | |
|
55 | 55 | PullRequestModel().close_pull_request( |
|
56 | 56 | pull_request_2, pull_request_2.author) |
|
57 | 57 | Session().commit() |
|
58 | 58 | |
|
59 | 59 | id_, params = build_data( |
|
60 | 60 | self.apikey, 'get_pull_requests', |
|
61 | 61 | repoid=pull_request.target_repo.repo_name, |
|
62 | 62 | status='new') |
|
63 | 63 | response = api_call(self.app, params) |
|
64 | 64 | assert response.status == '200 OK' |
|
65 | 65 | assert len(response.json['result']) == 1 |
|
66 | 66 | |
|
67 | 67 | id_, params = build_data( |
|
68 | 68 | self.apikey, 'get_pull_requests', |
|
69 | 69 | repoid=pull_request.target_repo.repo_name, |
|
70 | 70 | status='closed') |
|
71 | 71 | response = api_call(self.app, params) |
|
72 | 72 | assert response.status == '200 OK' |
|
73 | 73 | assert len(response.json['result']) == 1 |
|
74 | 74 | |
|
75 | 75 | @pytest.mark.backends("git", "hg") |
|
76 | 76 | def test_api_get_pull_requests_repo_error(self): |
|
77 | 77 | id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666) |
|
78 | 78 | response = api_call(self.app, params) |
|
79 | 79 | |
|
80 | 80 | expected = 'repository `666` does not exist' |
|
81 | 81 | assert_error(id_, expected, given=response.body) |
@@ -1,142 +1,142 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.meta import Session |
|
24 | 24 | from rhodecode.model.repo import RepoModel |
|
25 | 25 | from rhodecode.model.user import UserModel |
|
26 | 26 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
27 | 27 | from rhodecode.api.tests.utils import ( |
|
28 | 28 | build_data, api_call, assert_ok, assert_error, expected_permissions) |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | @pytest.mark.usefixtures("testuser_api", "app") |
|
32 | 32 | class TestGetRepo(object): |
|
33 | 33 | @pytest.mark.parametrize("apikey_attr, expect_secrets", [ |
|
34 | 34 | ('apikey', True), |
|
35 | 35 | ('apikey_regular', False), |
|
36 | 36 | ]) |
|
37 | 37 | @pytest.mark.parametrize("cache_param", [ |
|
38 | 38 | True, |
|
39 | 39 | False, |
|
40 | 40 | None, |
|
41 | 41 | ]) |
|
42 | 42 | def test_api_get_repo( |
|
43 | 43 | self, apikey_attr, expect_secrets, cache_param, backend, |
|
44 | 44 | user_util): |
|
45 | 45 | repo = backend.create_repo() |
|
46 | 46 | repo_id = repo.repo_id |
|
47 | 47 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
48 | 48 | group = user_util.create_user_group(members=[usr]) |
|
49 | 49 | user_util.grant_user_group_permission_to_repo( |
|
50 | 50 | repo=repo, user_group=group, permission_name='repository.read') |
|
51 | 51 | Session().commit() |
|
52 | 52 | kwargs = { |
|
53 | 53 | 'repoid': repo.repo_name, |
|
54 | 54 | } |
|
55 | 55 | if cache_param is not None: |
|
56 | 56 | kwargs['cache'] = cache_param |
|
57 | 57 | |
|
58 | 58 | apikey = getattr(self, apikey_attr) |
|
59 | 59 | id_, params = build_data(apikey, 'get_repo', **kwargs) |
|
60 | 60 | response = api_call(self.app, params) |
|
61 | 61 | |
|
62 | 62 | ret = repo.get_api_data() |
|
63 | 63 | |
|
64 | 64 | permissions = expected_permissions(repo) |
|
65 | 65 | |
|
66 | 66 | followers = [] |
|
67 | 67 | |
|
68 | 68 | repo = RepoModel().get(repo_id) |
|
69 | 69 | for user in repo.followers: |
|
70 | 70 | followers.append(user.user.get_api_data( |
|
71 | 71 | include_secrets=expect_secrets)) |
|
72 | 72 | |
|
73 | 73 | ret['permissions'] = permissions |
|
74 | 74 | ret['followers'] = followers |
|
75 | 75 | |
|
76 | 76 | expected = ret |
|
77 | 77 | |
|
78 | 78 | assert_ok(id_, expected, given=response.body) |
|
79 | 79 | |
|
80 | 80 | @pytest.mark.parametrize("grant_perm", [ |
|
81 | 81 | 'repository.admin', |
|
82 | 82 | 'repository.write', |
|
83 | 83 | 'repository.read', |
|
84 | 84 | ]) |
|
85 | 85 | def test_api_get_repo_by_non_admin(self, grant_perm, backend): |
|
86 | 86 | # TODO: Depending on which tests are running before this one, we |
|
87 | 87 | # start with a different number of permissions in the database. |
|
88 | 88 | repo = RepoModel().get_by_repo_name(backend.repo_name) |
|
89 | 89 | repo_id = repo.repo_id |
|
90 | 90 | permission_count = len(repo.repo_to_perm) |
|
91 | 91 | |
|
92 | 92 | RepoModel().grant_user_permission(repo=backend.repo_name, |
|
93 | 93 | user=self.TEST_USER_LOGIN, |
|
94 | 94 | perm=grant_perm) |
|
95 | 95 | Session().commit() |
|
96 | 96 | id_, params = build_data( |
|
97 | 97 | self.apikey_regular, 'get_repo', repoid=backend.repo_name) |
|
98 | 98 | response = api_call(self.app, params) |
|
99 | 99 | |
|
100 | 100 | repo = RepoModel().get_by_repo_name(backend.repo_name) |
|
101 | 101 | ret = repo.get_api_data() |
|
102 | 102 | |
|
103 | 103 | assert permission_count + 1, len(repo.repo_to_perm) |
|
104 | 104 | |
|
105 | 105 | permissions = expected_permissions(repo) |
|
106 | 106 | |
|
107 | 107 | followers = [] |
|
108 | 108 | |
|
109 | 109 | repo = RepoModel().get(repo_id) |
|
110 | 110 | for user in repo.followers: |
|
111 | 111 | followers.append(user.user.get_api_data()) |
|
112 | 112 | |
|
113 | 113 | ret['permissions'] = permissions |
|
114 | 114 | ret['followers'] = followers |
|
115 | 115 | |
|
116 | 116 | expected = ret |
|
117 | 117 | try: |
|
118 | 118 | assert_ok(id_, expected, given=response.body) |
|
119 | 119 | finally: |
|
120 | 120 | RepoModel().revoke_user_permission( |
|
121 | 121 | backend.repo_name, self.TEST_USER_LOGIN) |
|
122 | 122 | |
|
123 | 123 | def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend): |
|
124 | 124 | RepoModel().grant_user_permission(repo=backend.repo_name, |
|
125 | 125 | user=self.TEST_USER_LOGIN, |
|
126 | 126 | perm='repository.none') |
|
127 | 127 | |
|
128 | 128 | id_, params = build_data( |
|
129 | 129 | self.apikey_regular, 'get_repo', repoid=backend.repo_name) |
|
130 | 130 | response = api_call(self.app, params) |
|
131 | 131 | |
|
132 | 132 | expected = 'repository `%s` does not exist' % (backend.repo_name) |
|
133 | 133 | assert_error(id_, expected, given=response.body) |
|
134 | 134 | |
|
135 | 135 | def test_api_get_repo_not_existing(self): |
|
136 | 136 | id_, params = build_data( |
|
137 | 137 | self.apikey, 'get_repo', repoid='no-such-repo') |
|
138 | 138 | response = api_call(self.app, params) |
|
139 | 139 | |
|
140 | 140 | ret = 'repository `%s` does not exist' % 'no-such-repo' |
|
141 | 141 | expected = ret |
|
142 | 142 | assert_error(id_, expected, given=response.body) |
@@ -1,140 +1,140 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.api.tests.utils import build_data, api_call, assert_error |
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | @pytest.mark.usefixtures("testuser_api", "app") |
|
27 | 27 | class TestGetRepoChangeset(object): |
|
28 | 28 | @pytest.mark.parametrize("details", ['basic', 'extended', 'full']) |
|
29 | 29 | def test_get_repo_changeset(self, details, backend): |
|
30 | 30 | commit = backend.repo.get_commit(commit_idx=0) |
|
31 | 31 | __, params = build_data( |
|
32 | 32 | self.apikey, 'get_repo_changeset', |
|
33 | 33 | repoid=backend.repo_name, revision=commit.raw_id, |
|
34 | 34 | details=details, |
|
35 | 35 | ) |
|
36 | 36 | response = api_call(self.app, params) |
|
37 | 37 | result = response.json['result'] |
|
38 | 38 | assert result['revision'] == 0 |
|
39 | 39 | assert result['raw_id'] == commit.raw_id |
|
40 | 40 | |
|
41 | 41 | if details == 'full': |
|
42 | 42 | assert result['refs']['bookmarks'] == getattr( |
|
43 | 43 | commit, 'bookmarks', []) |
|
44 | 44 | branches = [commit.branch] if commit.branch else [] |
|
45 | 45 | assert result['refs']['branches'] == branches |
|
46 | 46 | assert result['refs']['tags'] == commit.tags |
|
47 | 47 | |
|
48 | 48 | @pytest.mark.parametrize("details", ['basic', 'extended', 'full']) |
|
49 | 49 | def test_get_repo_changeset_bad_type(self, details, backend): |
|
50 | 50 | id_, params = build_data( |
|
51 | 51 | self.apikey, 'get_repo_changeset', |
|
52 | 52 | repoid=backend.repo_name, revision=0, |
|
53 | 53 | details=details, |
|
54 | 54 | ) |
|
55 | 55 | response = api_call(self.app, params) |
|
56 | 56 | expected = "commit_id must be a string value got <class 'int'> instead" |
|
57 | 57 | assert_error(id_, expected, given=response.body) |
|
58 | 58 | |
|
59 | 59 | @pytest.mark.parametrize("details", ['basic', 'extended', 'full']) |
|
60 | 60 | def test_get_repo_changesets(self, details, backend): |
|
61 | 61 | limit = 2 |
|
62 | 62 | commit = backend.repo.get_commit(commit_idx=0) |
|
63 | 63 | __, params = build_data( |
|
64 | 64 | self.apikey, 'get_repo_changesets', |
|
65 | 65 | repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit, |
|
66 | 66 | details=details, |
|
67 | 67 | ) |
|
68 | 68 | response = api_call(self.app, params) |
|
69 | 69 | result = response.json['result'] |
|
70 | 70 | assert result |
|
71 | 71 | assert len(result) == limit |
|
72 | 72 | for x in range(limit): |
|
73 | 73 | assert result[x]['revision'] == x |
|
74 | 74 | |
|
75 | 75 | if details == 'full': |
|
76 | 76 | for x in range(limit): |
|
77 | 77 | assert 'bookmarks' in result[x]['refs'] |
|
78 | 78 | assert 'branches' in result[x]['refs'] |
|
79 | 79 | assert 'tags' in result[x]['refs'] |
|
80 | 80 | |
|
81 | 81 | @pytest.mark.parametrize("details", ['basic', 'extended', 'full']) |
|
82 | 82 | @pytest.mark.parametrize("start_rev, expected_revision", [ |
|
83 | 83 | ("0", 0), |
|
84 | 84 | ("10", 10), |
|
85 | 85 | ("20", 20), |
|
86 | 86 | ]) |
|
87 | 87 | @pytest.mark.backends("hg", "git") |
|
88 | 88 | def test_get_repo_changesets_commit_range( |
|
89 | 89 | self, details, backend, start_rev, expected_revision): |
|
90 | 90 | limit = 10 |
|
91 | 91 | __, params = build_data( |
|
92 | 92 | self.apikey, 'get_repo_changesets', |
|
93 | 93 | repoid=backend.repo_name, start_rev=start_rev, limit=limit, |
|
94 | 94 | details=details, |
|
95 | 95 | ) |
|
96 | 96 | response = api_call(self.app, params) |
|
97 | 97 | result = response.json['result'] |
|
98 | 98 | assert result |
|
99 | 99 | assert len(result) == limit |
|
100 | 100 | for i in range(limit): |
|
101 | 101 | assert result[i]['revision'] == int(expected_revision) + i |
|
102 | 102 | |
|
103 | 103 | @pytest.mark.parametrize("details", ['basic', 'extended', 'full']) |
|
104 | 104 | @pytest.mark.parametrize("start_rev, expected_revision", [ |
|
105 | 105 | ("0", 0), |
|
106 | 106 | ("10", 9), |
|
107 | 107 | ("20", 19), |
|
108 | 108 | ]) |
|
109 | 109 | def test_get_repo_changesets_commit_range_svn( |
|
110 | 110 | self, details, backend_svn, start_rev, expected_revision): |
|
111 | 111 | |
|
112 | 112 | # TODO: johbo: SVN showed a problem here: The parameter "start_rev" |
|
113 | 113 | # in our API allows to pass in a "Commit ID" as well as a |
|
114 | 114 | # "Commit Index". In the case of Subversion it is not possible to |
|
115 | 115 | # distinguish these cases. As a workaround we implemented this |
|
116 | 116 | # behavior which gives a preference to see it as a "Commit ID". |
|
117 | 117 | |
|
118 | 118 | limit = 10 |
|
119 | 119 | __, params = build_data( |
|
120 | 120 | self.apikey, 'get_repo_changesets', |
|
121 | 121 | repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit, |
|
122 | 122 | details=details, |
|
123 | 123 | ) |
|
124 | 124 | response = api_call(self.app, params) |
|
125 | 125 | result = response.json['result'] |
|
126 | 126 | assert result |
|
127 | 127 | assert len(result) == limit |
|
128 | 128 | for i in range(limit): |
|
129 | 129 | assert result[i]['revision'] == int(expected_revision) + i |
|
130 | 130 | |
|
131 | 131 | @pytest.mark.parametrize("details", ['basic', 'extended', 'full']) |
|
132 | 132 | def test_get_repo_changesets_bad_type(self, details, backend): |
|
133 | 133 | id_, params = build_data( |
|
134 | 134 | self.apikey, 'get_repo_changesets', |
|
135 | 135 | repoid=backend.repo_name, start_rev=0, limit=2, |
|
136 | 136 | details=details, |
|
137 | 137 | ) |
|
138 | 138 | response = api_call(self.app, params) |
|
139 | 139 | expected = "commit_id must be a string value got <class 'int'> instead" |
|
140 | 140 | assert_error(id_, expected, given=response.body) |
@@ -1,141 +1,141 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.db import User, ChangesetComment |
|
24 | 24 | from rhodecode.model.meta import Session |
|
25 | 25 | from rhodecode.model.comment import CommentsModel |
|
26 | 26 | from rhodecode.api.tests.utils import ( |
|
27 | 27 | build_data, api_call, assert_error, assert_call_ok) |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | @pytest.fixture() |
|
31 | 31 | def make_repo_comments_factory(request): |
|
32 | 32 | |
|
33 | 33 | class Make(object): |
|
34 | 34 | |
|
35 | 35 | def make_comments(self, repo): |
|
36 | 36 | user = User.get_first_super_admin() |
|
37 | 37 | commit = repo.scm_instance()[0] |
|
38 | 38 | |
|
39 | 39 | commit_id = commit.raw_id |
|
40 | 40 | file_0 = commit.affected_files[0] |
|
41 | 41 | comments = [] |
|
42 | 42 | |
|
43 | 43 | # general |
|
44 | 44 | comment = CommentsModel().create( |
|
45 | 45 | text='General Comment', repo=repo, user=user, commit_id=commit_id, |
|
46 | 46 | comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False) |
|
47 | 47 | comments.append(comment) |
|
48 | 48 | |
|
49 | 49 | # inline |
|
50 | 50 | comment = CommentsModel().create( |
|
51 | 51 | text='Inline Comment', repo=repo, user=user, commit_id=commit_id, |
|
52 | 52 | f_path=file_0, line_no='n1', |
|
53 | 53 | comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False) |
|
54 | 54 | comments.append(comment) |
|
55 | 55 | |
|
56 | 56 | # todo |
|
57 | 57 | comment = CommentsModel().create( |
|
58 | 58 | text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id, |
|
59 | 59 | f_path=file_0, line_no='n1', |
|
60 | 60 | comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False) |
|
61 | 61 | comments.append(comment) |
|
62 | 62 | |
|
63 | 63 | return comments |
|
64 | 64 | |
|
65 | 65 | return Make() |
|
66 | 66 | |
|
67 | 67 | |
|
68 | 68 | @pytest.mark.usefixtures("testuser_api", "app") |
|
69 | 69 | class TestGetRepo(object): |
|
70 | 70 | |
|
71 | 71 | @pytest.mark.parametrize('filters, expected_count', [ |
|
72 | 72 | ({}, 3), |
|
73 | 73 | ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2), |
|
74 | 74 | ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1), |
|
75 | 75 | ({'commit_id': 'FILLED DYNAMIC'}, 3), |
|
76 | 76 | ]) |
|
77 | 77 | def test_api_get_repo_comments(self, backend, user_util, |
|
78 | 78 | make_repo_comments_factory, filters, expected_count): |
|
79 | 79 | commits = [{'message': 'A'}, {'message': 'B'}] |
|
80 | 80 | repo = backend.create_repo(commits=commits) |
|
81 | 81 | make_repo_comments_factory.make_comments(repo) |
|
82 | 82 | |
|
83 | 83 | api_call_params = {'repoid': repo.repo_name,} |
|
84 | 84 | api_call_params.update(filters) |
|
85 | 85 | |
|
86 | 86 | if 'commit_id' in api_call_params: |
|
87 | 87 | commit = repo.scm_instance()[0] |
|
88 | 88 | commit_id = commit.raw_id |
|
89 | 89 | api_call_params['commit_id'] = commit_id |
|
90 | 90 | |
|
91 | 91 | id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params) |
|
92 | 92 | response = api_call(self.app, params) |
|
93 | 93 | result = assert_call_ok(id_, given=response.body) |
|
94 | 94 | |
|
95 | 95 | assert len(result) == expected_count |
|
96 | 96 | |
|
97 | 97 | def test_api_get_repo_comments_wrong_comment_type( |
|
98 | 98 | self, make_repo_comments_factory, backend_hg): |
|
99 | 99 | commits = [{'message': 'A'}, {'message': 'B'}] |
|
100 | 100 | repo = backend_hg.create_repo(commits=commits) |
|
101 | 101 | make_repo_comments_factory.make_comments(repo) |
|
102 | 102 | |
|
103 | 103 | api_call_params = {'repoid': repo.repo_name} |
|
104 | 104 | api_call_params.update({'comment_type': 'bogus'}) |
|
105 | 105 | |
|
106 | 106 | expected = 'comment_type must be one of `{}` got {}'.format( |
|
107 | 107 | ChangesetComment.COMMENT_TYPES, 'bogus') |
|
108 | 108 | id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params) |
|
109 | 109 | response = api_call(self.app, params) |
|
110 | 110 | assert_error(id_, expected, given=response.body) |
|
111 | 111 | |
|
112 | 112 | def test_api_get_comment(self, make_repo_comments_factory, backend_hg): |
|
113 | 113 | commits = [{'message': 'A'}, {'message': 'B'}] |
|
114 | 114 | repo = backend_hg.create_repo(commits=commits) |
|
115 | 115 | |
|
116 | 116 | comments = make_repo_comments_factory.make_comments(repo) |
|
117 | 117 | comment_ids = [x.comment_id for x in comments] |
|
118 | 118 | Session().commit() |
|
119 | 119 | |
|
120 | 120 | for comment_id in comment_ids: |
|
121 | 121 | id_, params = build_data(self.apikey, 'get_comment', |
|
122 | 122 | **{'comment_id': comment_id}) |
|
123 | 123 | response = api_call(self.app, params) |
|
124 | 124 | result = assert_call_ok(id_, given=response.body) |
|
125 | 125 | assert result['comment_id'] == comment_id |
|
126 | 126 | |
|
127 | 127 | def test_api_get_comment_no_access(self, make_repo_comments_factory, backend_hg, user_util): |
|
128 | 128 | commits = [{'message': 'A'}, {'message': 'B'}] |
|
129 | 129 | repo = backend_hg.create_repo(commits=commits) |
|
130 | 130 | comments = make_repo_comments_factory.make_comments(repo) |
|
131 | 131 | comment_id = comments[0].comment_id |
|
132 | 132 | |
|
133 | 133 | test_user = user_util.create_user() |
|
134 | 134 | user_util.grant_user_permission_to_repo(repo, test_user, 'repository.none') |
|
135 | 135 | |
|
136 | 136 | id_, params = build_data(test_user.api_key, 'get_comment', |
|
137 | 137 | **{'comment_id': comment_id}) |
|
138 | 138 | response = api_call(self.app, params) |
|
139 | 139 | assert_error(id_, |
|
140 | 140 | expected='comment `{}` does not exist'.format(comment_id), |
|
141 | 141 | given=response.body) |
@@ -1,54 +1,54 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo_group import RepoGroupModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_ok, assert_error, expected_permissions) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestApiGetRepoGroup(object): |
|
30 | 30 | def test_api_get_repo_group(self, user_util): |
|
31 | 31 | repo_group = user_util.create_repo_group() |
|
32 | 32 | repo_group_name = repo_group.group_name |
|
33 | 33 | |
|
34 | 34 | id_, params = build_data( |
|
35 | 35 | self.apikey, 'get_repo_group', repogroupid=repo_group_name) |
|
36 | 36 | response = api_call(self.app, params) |
|
37 | 37 | |
|
38 | 38 | repo_group = RepoGroupModel()._get_repo_group(repo_group_name) |
|
39 | 39 | ret = repo_group.get_api_data() |
|
40 | 40 | |
|
41 | 41 | permissions = expected_permissions(repo_group) |
|
42 | 42 | |
|
43 | 43 | ret['permissions'] = permissions |
|
44 | 44 | expected = ret |
|
45 | 45 | assert_ok(id_, expected, given=response.body) |
|
46 | 46 | |
|
47 | 47 | def test_api_get_repo_group_not_existing(self): |
|
48 | 48 | id_, params = build_data( |
|
49 | 49 | self.apikey, 'get_repo_group', repogroupid='no-such-repo-group') |
|
50 | 50 | response = api_call(self.app, params) |
|
51 | 51 | |
|
52 | 52 | ret = 'repository group `%s` does not exist' % 'no-such-repo-group' |
|
53 | 53 | expected = ret |
|
54 | 54 | assert_error(id_, expected, given=response.body) |
@@ -1,39 +1,39 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo_group import RepoGroupModel |
|
24 | 24 | from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | @pytest.mark.usefixtures("testuser_api", "app") |
|
28 | 28 | class TestApiGetRepoGroups(object): |
|
29 | 29 | def test_api_get_repo_groups(self): |
|
30 | 30 | id_, params = build_data(self.apikey, 'get_repo_groups') |
|
31 | 31 | response = api_call(self.app, params) |
|
32 | 32 | |
|
33 | 33 | result = [] |
|
34 | 34 | for repo in RepoGroupModel().get_all(): |
|
35 | 35 | result.append(repo.get_api_data()) |
|
36 | 36 | ret = jsonify(result) |
|
37 | 37 | |
|
38 | 38 | expected = ret |
|
39 | 39 | assert_ok(id_, expected, given=response.body) |
@@ -1,141 +1,141 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.meta import Session |
|
24 | 24 | from rhodecode.model.repo import RepoModel |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestGetRepoNodes(object): |
|
31 | 31 | @pytest.mark.parametrize("name, ret_type", [ |
|
32 | 32 | ('all', 'all'), |
|
33 | 33 | ('dirs', 'dirs'), |
|
34 | 34 | ('files', 'files'), |
|
35 | 35 | ]) |
|
36 | 36 | def test_api_get_repo_nodes(self, name, ret_type, backend): |
|
37 | 37 | commit_id = 'tip' |
|
38 | 38 | path = '/' |
|
39 | 39 | id_, params = build_data( |
|
40 | 40 | self.apikey, 'get_repo_nodes', |
|
41 | 41 | repoid=backend.repo_name, revision=commit_id, |
|
42 | 42 | root_path=path, |
|
43 | 43 | ret_type=ret_type) |
|
44 | 44 | response = api_call(self.app, params) |
|
45 | 45 | |
|
46 | 46 | # we don't the actual return types here since it's tested somewhere |
|
47 | 47 | # else |
|
48 | 48 | expected = response.json['result'] |
|
49 | 49 | assert_ok(id_, expected, given=response.body) |
|
50 | 50 | |
|
51 | 51 | def test_api_get_repo_nodes_bad_commits(self, backend): |
|
52 | 52 | commit_id = 'i-dont-exist' |
|
53 | 53 | path = '/' |
|
54 | 54 | id_, params = build_data( |
|
55 | 55 | self.apikey, 'get_repo_nodes', |
|
56 | 56 | repoid=backend.repo_name, revision=commit_id, |
|
57 | 57 | root_path=path, ) |
|
58 | 58 | response = api_call(self.app, params) |
|
59 | 59 | |
|
60 | 60 | expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,) |
|
61 | 61 | assert_error(id_, expected, given=response.body) |
|
62 | 62 | |
|
63 | 63 | def test_api_get_repo_nodes_bad_path(self, backend): |
|
64 | 64 | commit_id = 'tip' |
|
65 | 65 | path = '/idontexits' |
|
66 | 66 | id_, params = build_data( |
|
67 | 67 | self.apikey, 'get_repo_nodes', |
|
68 | 68 | repoid=backend.repo_name, revision=commit_id, |
|
69 | 69 | root_path=path, ) |
|
70 | 70 | response = api_call(self.app, params) |
|
71 | 71 | |
|
72 | 72 | expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,) |
|
73 | 73 | assert_error(id_, expected, given=response.body) |
|
74 | 74 | |
|
75 | 75 | def test_api_get_repo_nodes_max_file_bytes(self, backend): |
|
76 | 76 | commit_id = 'tip' |
|
77 | 77 | path = '/' |
|
78 | 78 | max_file_bytes = 500 |
|
79 | 79 | |
|
80 | 80 | id_, params = build_data( |
|
81 | 81 | self.apikey, 'get_repo_nodes', |
|
82 | 82 | repoid=backend.repo_name, revision=commit_id, details='full', |
|
83 | 83 | root_path=path) |
|
84 | 84 | response = api_call(self.app, params) |
|
85 | 85 | assert any(file['content'] and len(file['content']) > max_file_bytes |
|
86 | 86 | for file in response.json['result']) |
|
87 | 87 | |
|
88 | 88 | id_, params = build_data( |
|
89 | 89 | self.apikey, 'get_repo_nodes', |
|
90 | 90 | repoid=backend.repo_name, revision=commit_id, |
|
91 | 91 | root_path=path, details='full', |
|
92 | 92 | max_file_bytes=max_file_bytes) |
|
93 | 93 | response = api_call(self.app, params) |
|
94 | 94 | assert all( |
|
95 | 95 | file['content'] is None if file['size'] > max_file_bytes else True |
|
96 | 96 | for file in response.json['result']) |
|
97 | 97 | |
|
98 | 98 | def test_api_get_repo_nodes_bad_ret_type(self, backend): |
|
99 | 99 | commit_id = 'tip' |
|
100 | 100 | path = '/' |
|
101 | 101 | ret_type = 'error' |
|
102 | 102 | id_, params = build_data( |
|
103 | 103 | self.apikey, 'get_repo_nodes', |
|
104 | 104 | repoid=backend.repo_name, revision=commit_id, |
|
105 | 105 | root_path=path, |
|
106 | 106 | ret_type=ret_type) |
|
107 | 107 | response = api_call(self.app, params) |
|
108 | 108 | |
|
109 | 109 | expected = ('ret_type must be one of %s' |
|
110 | 110 | % (','.join(['all', 'dirs', 'files']))) |
|
111 | 111 | assert_error(id_, expected, given=response.body) |
|
112 | 112 | |
|
113 | 113 | @pytest.mark.parametrize("name, ret_type, grant_perm", [ |
|
114 | 114 | ('all', 'all', 'repository.write'), |
|
115 | 115 | ('dirs', 'dirs', 'repository.admin'), |
|
116 | 116 | ('files', 'files', 'repository.read'), |
|
117 | 117 | ]) |
|
118 | 118 | def test_api_get_repo_nodes_by_regular_user( |
|
119 | 119 | self, name, ret_type, grant_perm, backend): |
|
120 | 120 | RepoModel().grant_user_permission(repo=backend.repo_name, |
|
121 | 121 | user=self.TEST_USER_LOGIN, |
|
122 | 122 | perm=grant_perm) |
|
123 | 123 | Session().commit() |
|
124 | 124 | |
|
125 | 125 | commit_id = 'tip' |
|
126 | 126 | path = '/' |
|
127 | 127 | id_, params = build_data( |
|
128 | 128 | self.apikey_regular, 'get_repo_nodes', |
|
129 | 129 | repoid=backend.repo_name, revision=commit_id, |
|
130 | 130 | root_path=path, |
|
131 | 131 | ret_type=ret_type) |
|
132 | 132 | response = api_call(self.app, params) |
|
133 | 133 | |
|
134 | 134 | # we don't the actual return types here since it's tested somewhere |
|
135 | 135 | # else |
|
136 | 136 | expected = response.json['result'] |
|
137 | 137 | try: |
|
138 | 138 | assert_ok(id_, expected, given=response.body) |
|
139 | 139 | finally: |
|
140 | 140 | RepoModel().revoke_user_permission( |
|
141 | 141 | backend.repo_name, self.TEST_USER_LOGIN) |
@@ -1,39 +1,39 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.meta import Session |
|
24 | 24 | from rhodecode.model.repo import RepoModel |
|
25 | 25 | from rhodecode.model.user import UserModel |
|
26 | 26 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
27 | 27 | from rhodecode.api.tests.utils import ( |
|
28 | 28 | build_data, api_call, assert_ok, assert_error, expected_permissions) |
|
29 | 29 | |
|
30 | 30 | |
|
31 | 31 | @pytest.mark.usefixtures("testuser_api", "app") |
|
32 | 32 | class TestGetRepo(object): |
|
33 | 33 | def test_api_get_repo_refs(self, backend, user_util): |
|
34 | 34 | repo = backend.create_repo() |
|
35 | 35 | id_, params = build_data(self.apikey, 'get_repo_refs', |
|
36 | 36 | **{'repoid': repo.repo_name,}) |
|
37 | 37 | response = api_call(self.app, params) |
|
38 | 38 | expected = repo.scm_instance().refs() |
|
39 | 39 | assert_ok(id_, expected, given=response.body) |
@@ -1,128 +1,128 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo import RepoModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_ok, assert_error, jsonify) |
|
26 | 26 | from rhodecode.model.db import User |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestGetRepos(object): |
|
31 | 31 | def test_api_get_repos(self): |
|
32 | 32 | id_, params = build_data(self.apikey, 'get_repos') |
|
33 | 33 | response = api_call(self.app, params) |
|
34 | 34 | |
|
35 | 35 | result = [] |
|
36 | 36 | for repo in RepoModel().get_all(): |
|
37 | 37 | result.append(repo.get_api_data(include_secrets=True)) |
|
38 | 38 | ret = jsonify(result) |
|
39 | 39 | |
|
40 | 40 | expected = ret |
|
41 | 41 | assert_ok(id_, expected, given=response.body) |
|
42 | 42 | |
|
43 | 43 | def test_api_get_repos_only_toplevel(self, user_util): |
|
44 | 44 | repo_group = user_util.create_repo_group(auto_cleanup=True) |
|
45 | 45 | user_util.create_repo(parent=repo_group) |
|
46 | 46 | |
|
47 | 47 | id_, params = build_data(self.apikey, 'get_repos', traverse=0) |
|
48 | 48 | response = api_call(self.app, params) |
|
49 | 49 | |
|
50 | 50 | result = [] |
|
51 | 51 | for repo in RepoModel().get_repos_for_root(root=None): |
|
52 | 52 | result.append(repo.get_api_data(include_secrets=True)) |
|
53 | 53 | expected = jsonify(result) |
|
54 | 54 | |
|
55 | 55 | assert_ok(id_, expected, given=response.body) |
|
56 | 56 | |
|
57 | 57 | def test_api_get_repos_with_wrong_root(self): |
|
58 | 58 | id_, params = build_data(self.apikey, 'get_repos', root='abracadabra') |
|
59 | 59 | response = api_call(self.app, params) |
|
60 | 60 | |
|
61 | 61 | expected = 'Root repository group `abracadabra` does not exist' |
|
62 | 62 | assert_error(id_, expected, given=response.body) |
|
63 | 63 | |
|
64 | 64 | def test_api_get_repos_with_root(self, user_util): |
|
65 | 65 | repo_group = user_util.create_repo_group(auto_cleanup=True) |
|
66 | 66 | repo_group_name = repo_group.group_name |
|
67 | 67 | |
|
68 | 68 | user_util.create_repo(parent=repo_group) |
|
69 | 69 | user_util.create_repo(parent=repo_group) |
|
70 | 70 | |
|
71 | 71 | # nested, should not show up |
|
72 | 72 | user_util._test_name = '{}/'.format(repo_group_name) |
|
73 | 73 | sub_repo_group = user_util.create_repo_group(auto_cleanup=True) |
|
74 | 74 | user_util.create_repo(parent=sub_repo_group) |
|
75 | 75 | |
|
76 | 76 | id_, params = build_data(self.apikey, 'get_repos', |
|
77 | 77 | root=repo_group_name, traverse=0) |
|
78 | 78 | response = api_call(self.app, params) |
|
79 | 79 | |
|
80 | 80 | result = [] |
|
81 | 81 | for repo in RepoModel().get_repos_for_root(repo_group): |
|
82 | 82 | result.append(repo.get_api_data(include_secrets=True)) |
|
83 | 83 | |
|
84 | 84 | assert len(result) == 2 |
|
85 | 85 | expected = jsonify(result) |
|
86 | 86 | assert_ok(id_, expected, given=response.body) |
|
87 | 87 | |
|
88 | 88 | def test_api_get_repos_with_root_and_traverse(self, user_util): |
|
89 | 89 | repo_group = user_util.create_repo_group(auto_cleanup=True) |
|
90 | 90 | repo_group_name = repo_group.group_name |
|
91 | 91 | |
|
92 | 92 | user_util.create_repo(parent=repo_group) |
|
93 | 93 | user_util.create_repo(parent=repo_group) |
|
94 | 94 | |
|
95 | 95 | # nested, should not show up |
|
96 | 96 | user_util._test_name = '{}/'.format(repo_group_name) |
|
97 | 97 | sub_repo_group = user_util.create_repo_group(auto_cleanup=True) |
|
98 | 98 | user_util.create_repo(parent=sub_repo_group) |
|
99 | 99 | |
|
100 | 100 | id_, params = build_data(self.apikey, 'get_repos', |
|
101 | 101 | root=repo_group_name, traverse=1) |
|
102 | 102 | response = api_call(self.app, params) |
|
103 | 103 | |
|
104 | 104 | result = [] |
|
105 | 105 | for repo in RepoModel().get_repos_for_root( |
|
106 | 106 | repo_group_name, traverse=True): |
|
107 | 107 | result.append(repo.get_api_data(include_secrets=True)) |
|
108 | 108 | |
|
109 | 109 | assert len(result) == 3 |
|
110 | 110 | expected = jsonify(result) |
|
111 | 111 | assert_ok(id_, expected, given=response.body) |
|
112 | 112 | |
|
113 | 113 | def test_api_get_repos_non_admin(self): |
|
114 | 114 | id_, params = build_data(self.apikey_regular, 'get_repos') |
|
115 | 115 | response = api_call(self.app, params) |
|
116 | 116 | |
|
117 | 117 | user = User.get_by_username(self.TEST_USER_LOGIN) |
|
118 | 118 | allowed_repos = user.AuthUser().permissions['repositories'] |
|
119 | 119 | |
|
120 | 120 | result = [] |
|
121 | 121 | for repo in RepoModel().get_all(): |
|
122 | 122 | perm = allowed_repos[repo.repo_name] |
|
123 | 123 | if perm in ['repository.read', 'repository.write', 'repository.admin']: |
|
124 | 124 | result.append(repo.get_api_data()) |
|
125 | 125 | ret = jsonify(result) |
|
126 | 126 | |
|
127 | 127 | expected = ret |
|
128 | 128 | assert_ok(id_, expected, given=response.body) |
@@ -1,83 +1,83 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.scm import ScmModel |
|
24 | 24 | from rhodecode.api.tests.utils import build_data, api_call, assert_ok |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | @pytest.fixture() |
|
28 | 28 | def http_host_stub(): |
|
29 | 29 | """ |
|
30 | 30 | To ensure that we can get an IP address, this test shall run with a |
|
31 | 31 | hostname set to "localhost". |
|
32 | 32 | """ |
|
33 | 33 | return 'localhost:80' |
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | @pytest.mark.usefixtures("testuser_api", "app") |
|
37 | 37 | class TestGetServerInfo(object): |
|
38 | 38 | def test_api_get_server_info(self): |
|
39 | 39 | id_, params = build_data(self.apikey, 'get_server_info') |
|
40 | 40 | response = api_call(self.app, params) |
|
41 | 41 | resp = response.json |
|
42 | 42 | expected = ScmModel().get_server_info() |
|
43 | 43 | expected['memory'] = resp['result']['memory'] |
|
44 | 44 | expected['uptime'] = resp['result']['uptime'] |
|
45 | 45 | expected['load'] = resp['result']['load'] |
|
46 | 46 | expected['cpu'] = resp['result']['cpu'] |
|
47 | 47 | expected['storage'] = resp['result']['storage'] |
|
48 | 48 | expected['storage_temp'] = resp['result']['storage_temp'] |
|
49 | 49 | expected['storage_inodes'] = resp['result']['storage_inodes'] |
|
50 | 50 | expected['server'] = resp['result']['server'] |
|
51 | 51 | |
|
52 | 52 | expected['index_storage'] = resp['result']['index_storage'] |
|
53 | 53 | expected['storage'] = resp['result']['storage'] |
|
54 | 54 | |
|
55 | 55 | assert_ok(id_, expected, given=response.body) |
|
56 | 56 | |
|
57 | 57 | def test_api_get_server_info_ip(self): |
|
58 | 58 | id_, params = build_data(self.apikey, 'get_server_info') |
|
59 | 59 | response = api_call(self.app, params) |
|
60 | 60 | resp = response.json |
|
61 | 61 | expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'}) |
|
62 | 62 | expected['memory'] = resp['result']['memory'] |
|
63 | 63 | expected['uptime'] = resp['result']['uptime'] |
|
64 | 64 | expected['load'] = resp['result']['load'] |
|
65 | 65 | expected['cpu'] = resp['result']['cpu'] |
|
66 | 66 | expected['storage'] = resp['result']['storage'] |
|
67 | 67 | expected['storage_temp'] = resp['result']['storage_temp'] |
|
68 | 68 | expected['storage_inodes'] = resp['result']['storage_inodes'] |
|
69 | 69 | expected['server'] = resp['result']['server'] |
|
70 | 70 | |
|
71 | 71 | expected['index_storage'] = resp['result']['index_storage'] |
|
72 | 72 | expected['storage'] = resp['result']['storage'] |
|
73 | 73 | |
|
74 | 74 | assert_ok(id_, expected, given=response.body) |
|
75 | 75 | |
|
76 | 76 | def test_api_get_server_info_data_for_search_index_build(self): |
|
77 | 77 | id_, params = build_data(self.apikey, 'get_server_info') |
|
78 | 78 | response = api_call(self.app, params) |
|
79 | 79 | resp = response.json |
|
80 | 80 | |
|
81 | 81 | # required by indexer |
|
82 | 82 | assert resp['result']['index_storage'] |
|
83 | 83 | assert resp['result']['storage'] |
@@ -1,85 +1,85 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.lib.auth import AuthUser |
|
23 | 23 | from rhodecode.model.user import UserModel |
|
24 | 24 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_ok, assert_error) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestGetUser(object): |
|
31 | 31 | def test_api_get_user(self): |
|
32 | 32 | id_, params = build_data( |
|
33 | 33 | self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN) |
|
34 | 34 | response = api_call(self.app, params) |
|
35 | 35 | |
|
36 | 36 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
37 | 37 | ret = usr.get_api_data(include_secrets=True) |
|
38 | 38 | permissions = AuthUser(usr.user_id).permissions |
|
39 | 39 | ret['permissions'] = permissions |
|
40 | 40 | ret['permissions_summary'] = permissions |
|
41 | 41 | |
|
42 | 42 | expected = ret |
|
43 | 43 | assert_ok(id_, expected, given=response.body) |
|
44 | 44 | |
|
45 | 45 | def test_api_get_user_not_existing(self): |
|
46 | 46 | id_, params = build_data(self.apikey, 'get_user', userid='trololo') |
|
47 | 47 | response = api_call(self.app, params) |
|
48 | 48 | |
|
49 | 49 | expected = "user `%s` does not exist" % 'trololo' |
|
50 | 50 | assert_error(id_, expected, given=response.body) |
|
51 | 51 | |
|
52 | 52 | def test_api_get_user_without_giving_userid(self): |
|
53 | 53 | id_, params = build_data(self.apikey, 'get_user') |
|
54 | 54 | response = api_call(self.app, params) |
|
55 | 55 | |
|
56 | 56 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
57 | 57 | ret = usr.get_api_data(include_secrets=True) |
|
58 | 58 | permissions = AuthUser(usr.user_id).permissions |
|
59 | 59 | ret['permissions'] = permissions |
|
60 | 60 | ret['permissions_summary'] = permissions |
|
61 | 61 | |
|
62 | 62 | expected = ret |
|
63 | 63 | assert_ok(id_, expected, given=response.body) |
|
64 | 64 | |
|
65 | 65 | def test_api_get_user_without_giving_userid_non_admin(self): |
|
66 | 66 | id_, params = build_data(self.apikey_regular, 'get_user') |
|
67 | 67 | response = api_call(self.app, params) |
|
68 | 68 | |
|
69 | 69 | usr = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
70 | 70 | ret = usr.get_api_data(include_secrets=True) |
|
71 | 71 | permissions = AuthUser(usr.user_id).permissions |
|
72 | 72 | ret['permissions'] = permissions |
|
73 | 73 | ret['permissions_summary'] = permissions |
|
74 | 74 | |
|
75 | 75 | expected = ret |
|
76 | 76 | assert_ok(id_, expected, given=response.body) |
|
77 | 77 | |
|
78 | 78 | def test_api_get_user_with_giving_userid_non_admin(self): |
|
79 | 79 | id_, params = build_data( |
|
80 | 80 | self.apikey_regular, 'get_user', |
|
81 | 81 | userid=self.TEST_USER_LOGIN) |
|
82 | 82 | response = api_call(self.app, params) |
|
83 | 83 | |
|
84 | 84 | expected = 'userid is not the same as your user' |
|
85 | 85 | assert_error(id_, expected, given=response.body) |
@@ -1,75 +1,75 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.user import UserModel |
|
23 | 23 | from rhodecode.api.tests.utils import ( |
|
24 | 24 | build_data, api_call, assert_ok, assert_error, expected_permissions) |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | @pytest.mark.usefixtures("testuser_api", "app") |
|
28 | 28 | class TestGetUserGroups(object): |
|
29 | 29 | def test_api_get_user_group(self, user_util): |
|
30 | 30 | user, group = user_util.create_user_with_group() |
|
31 | 31 | id_, params = build_data( |
|
32 | 32 | self.apikey, 'get_user_group', usergroupid=group.users_group_name) |
|
33 | 33 | response = api_call(self.app, params) |
|
34 | 34 | |
|
35 | 35 | ret = group.get_api_data() |
|
36 | 36 | ret['users'] = [user.get_api_data()] |
|
37 | 37 | |
|
38 | 38 | permissions = expected_permissions(group) |
|
39 | 39 | |
|
40 | 40 | ret['permissions'] = permissions |
|
41 | 41 | ret['permissions_summary'] = response.json['result']['permissions_summary'] |
|
42 | 42 | expected = ret |
|
43 | 43 | assert_ok(id_, expected, given=response.body) |
|
44 | 44 | |
|
45 | 45 | def test_api_get_user_group_regular_user(self, user_util): |
|
46 | 46 | user, group = user_util.create_user_with_group() |
|
47 | 47 | id_, params = build_data( |
|
48 | 48 | self.apikey_regular, 'get_user_group', |
|
49 | 49 | usergroupid=group.users_group_name) |
|
50 | 50 | response = api_call(self.app, params) |
|
51 | 51 | |
|
52 | 52 | ret = group.get_api_data() |
|
53 | 53 | ret['users'] = [user.get_api_data()] |
|
54 | 54 | |
|
55 | 55 | permissions = expected_permissions(group) |
|
56 | 56 | |
|
57 | 57 | ret['permissions'] = permissions |
|
58 | 58 | ret['permissions_summary'] = response.json['result']['permissions_summary'] |
|
59 | 59 | expected = ret |
|
60 | 60 | assert_ok(id_, expected, given=response.body) |
|
61 | 61 | |
|
62 | 62 | def test_api_get_user_group_regular_user_permission_denied( |
|
63 | 63 | self, user_util): |
|
64 | 64 | group = user_util.create_user_group() |
|
65 | 65 | user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
66 | 66 | group_name = group.users_group_name |
|
67 | 67 | user_util.grant_user_permission_to_user_group( |
|
68 | 68 | group, user, 'usergroup.none') |
|
69 | 69 | |
|
70 | 70 | id_, params = build_data( |
|
71 | 71 | self.apikey_regular, 'get_user_group', usergroupid=group_name) |
|
72 | 72 | response = api_call(self.app, params) |
|
73 | 73 | |
|
74 | 74 | expected = 'user group `%s` does not exist' % (group_name,) |
|
75 | 75 | assert_error(id_, expected, given=response.body) |
@@ -1,69 +1,69 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.user import UserModel |
|
24 | 24 | from rhodecode.api.tests.utils import build_data, api_call |
|
25 | 25 | from rhodecode.lib.ext_json import json |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestGetUserGroups(object): |
|
30 | 30 | @pytest.mark.parametrize("apikey_attr, expect_secrets", [ |
|
31 | 31 | ('apikey', True), |
|
32 | 32 | ('apikey_regular', False), |
|
33 | 33 | ]) |
|
34 | 34 | def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util): |
|
35 | 35 | first_group = user_util.create_user_group() |
|
36 | 36 | second_group = user_util.create_user_group() |
|
37 | 37 | expected = [ |
|
38 | 38 | g.get_api_data(include_secrets=expect_secrets) |
|
39 | 39 | for g in (first_group, second_group)] |
|
40 | 40 | |
|
41 | 41 | apikey = getattr(self, apikey_attr) |
|
42 | 42 | id_, params = build_data(apikey, 'get_user_groups', ) |
|
43 | 43 | response = api_call(self.app, params) |
|
44 | 44 | self._assert_ok(id_, expected, response) |
|
45 | 45 | |
|
46 | 46 | def test_api_get_user_groups_regular_user(self, user_util): |
|
47 | 47 | first_group = user_util.create_user_group() |
|
48 | 48 | second_group = user_util.create_user_group() |
|
49 | 49 | expected = [g.get_api_data() for g in (first_group, second_group)] |
|
50 | 50 | |
|
51 | 51 | id_, params = build_data(self.apikey_regular, 'get_user_groups', ) |
|
52 | 52 | response = api_call(self.app, params) |
|
53 | 53 | self._assert_ok(id_, expected, response) |
|
54 | 54 | |
|
55 | 55 | def test_api_get_user_groups_regular_user_no_permission(self, user_util): |
|
56 | 56 | group = user_util.create_user_group() |
|
57 | 57 | user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
58 | 58 | user_util.grant_user_permission_to_user_group( |
|
59 | 59 | group, user, 'usergroup.none') |
|
60 | 60 | id_, params = build_data(self.apikey_regular, 'get_user_groups', ) |
|
61 | 61 | response = api_call(self.app, params) |
|
62 | 62 | expected = [] |
|
63 | 63 | self._assert_ok(id_, expected, response) |
|
64 | 64 | |
|
65 | 65 | def _assert_ok(self, id_, expected_list, response): |
|
66 | 66 | result = json.loads(response.body) |
|
67 | 67 | assert result['id'] == id_ |
|
68 | 68 | assert result['error'] is None |
|
69 | 69 | assert result['result'] == expected_list |
@@ -1,39 +1,39 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.db import User |
|
23 | 23 | from rhodecode.api.tests.utils import ( |
|
24 | 24 | build_data, api_call, assert_ok, jsonify) |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | @pytest.mark.usefixtures("testuser_api", "app") |
|
28 | 28 | class TestGetUsers(object): |
|
29 | 29 | def test_api_get_users(self): |
|
30 | 30 | id_, params = build_data(self.apikey, 'get_users', ) |
|
31 | 31 | response = api_call(self.app, params) |
|
32 | 32 | ret_all = [] |
|
33 | 33 | _users = User.query().filter(User.username != User.DEFAULT_USER) \ |
|
34 | 34 | .order_by(User.username).all() |
|
35 | 35 | for usr in _users: |
|
36 | 36 | ret = usr.get_api_data(include_secrets=True) |
|
37 | 37 | ret_all.append(jsonify(ret)) |
|
38 | 38 | expected = ret_all |
|
39 | 39 | assert_ok(id_, expected, given=response.body) |
@@ -1,89 +1,89 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo import RepoModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestGrantUserGroupPermission(object): |
|
30 | 30 | @pytest.mark.parametrize("name, perm", [ |
|
31 | 31 | ('none', 'repository.none'), |
|
32 | 32 | ('read', 'repository.read'), |
|
33 | 33 | ('write', 'repository.write'), |
|
34 | 34 | ('admin', 'repository.admin') |
|
35 | 35 | ]) |
|
36 | 36 | def test_api_grant_user_group_permission( |
|
37 | 37 | self, name, perm, backend, user_util): |
|
38 | 38 | user_group = user_util.create_user_group() |
|
39 | 39 | id_, params = build_data( |
|
40 | 40 | self.apikey, |
|
41 | 41 | 'grant_user_group_permission', |
|
42 | 42 | repoid=backend.repo_name, |
|
43 | 43 | usergroupid=user_group.users_group_name, |
|
44 | 44 | perm=perm) |
|
45 | 45 | response = api_call(self.app, params) |
|
46 | 46 | |
|
47 | 47 | ret = { |
|
48 | 48 | 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % ( |
|
49 | 49 | perm, user_group.users_group_name, backend.repo_name |
|
50 | 50 | ), |
|
51 | 51 | 'success': True |
|
52 | 52 | } |
|
53 | 53 | expected = ret |
|
54 | 54 | assert_ok(id_, expected, given=response.body) |
|
55 | 55 | |
|
56 | 56 | def test_api_grant_user_group_permission_wrong_permission( |
|
57 | 57 | self, backend, user_util): |
|
58 | 58 | perm = 'haha.no.permission' |
|
59 | 59 | user_group = user_util.create_user_group() |
|
60 | 60 | id_, params = build_data( |
|
61 | 61 | self.apikey, |
|
62 | 62 | 'grant_user_group_permission', |
|
63 | 63 | repoid=backend.repo_name, |
|
64 | 64 | usergroupid=user_group.users_group_name, |
|
65 | 65 | perm=perm) |
|
66 | 66 | response = api_call(self.app, params) |
|
67 | 67 | |
|
68 | 68 | expected = 'permission `%s` does not exist.' % (perm,) |
|
69 | 69 | assert_error(id_, expected, given=response.body) |
|
70 | 70 | |
|
71 | 71 | @mock.patch.object(RepoModel, 'grant_user_group_permission', crash) |
|
72 | 72 | def test_api_grant_user_group_permission_exception_when_adding( |
|
73 | 73 | self, backend, user_util): |
|
74 | 74 | perm = 'repository.read' |
|
75 | 75 | user_group = user_util.create_user_group() |
|
76 | 76 | id_, params = build_data( |
|
77 | 77 | self.apikey, |
|
78 | 78 | 'grant_user_group_permission', |
|
79 | 79 | repoid=backend.repo_name, |
|
80 | 80 | usergroupid=user_group.users_group_name, |
|
81 | 81 | perm=perm) |
|
82 | 82 | response = api_call(self.app, params) |
|
83 | 83 | |
|
84 | 84 | expected = ( |
|
85 | 85 | 'failed to edit permission for user group: `%s` in repo: `%s`' % ( |
|
86 | 86 | user_group.users_group_name, backend.repo_name |
|
87 | 87 | ) |
|
88 | 88 | ) |
|
89 | 89 | assert_error(id_, expected, given=response.body) |
@@ -1,172 +1,172 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.user import UserModel |
|
24 | 24 | from rhodecode.model.repo_group import RepoGroupModel |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok, crash) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestGrantUserGroupPermissionFromRepoGroup(object): |
|
31 | 31 | @pytest.mark.parametrize("name, perm, apply_to_children", [ |
|
32 | 32 | ('none', 'group.none', 'none'), |
|
33 | 33 | ('read', 'group.read', 'none'), |
|
34 | 34 | ('write', 'group.write', 'none'), |
|
35 | 35 | ('admin', 'group.admin', 'none'), |
|
36 | 36 | |
|
37 | 37 | ('none', 'group.none', 'all'), |
|
38 | 38 | ('read', 'group.read', 'all'), |
|
39 | 39 | ('write', 'group.write', 'all'), |
|
40 | 40 | ('admin', 'group.admin', 'all'), |
|
41 | 41 | |
|
42 | 42 | ('none', 'group.none', 'repos'), |
|
43 | 43 | ('read', 'group.read', 'repos'), |
|
44 | 44 | ('write', 'group.write', 'repos'), |
|
45 | 45 | ('admin', 'group.admin', 'repos'), |
|
46 | 46 | |
|
47 | 47 | ('none', 'group.none', 'groups'), |
|
48 | 48 | ('read', 'group.read', 'groups'), |
|
49 | 49 | ('write', 'group.write', 'groups'), |
|
50 | 50 | ('admin', 'group.admin', 'groups'), |
|
51 | 51 | ]) |
|
52 | 52 | def test_api_grant_user_group_permission_to_repo_group( |
|
53 | 53 | self, name, perm, apply_to_children, user_util): |
|
54 | 54 | user_group = user_util.create_user_group() |
|
55 | 55 | repo_group = user_util.create_repo_group() |
|
56 | 56 | user_util.create_repo(parent=repo_group) |
|
57 | 57 | |
|
58 | 58 | id_, params = build_data( |
|
59 | 59 | self.apikey, |
|
60 | 60 | 'grant_user_group_permission_to_repo_group', |
|
61 | 61 | repogroupid=repo_group.name, |
|
62 | 62 | usergroupid=user_group.users_group_name, |
|
63 | 63 | perm=perm, |
|
64 | 64 | apply_to_children=apply_to_children,) |
|
65 | 65 | response = api_call(self.app, params) |
|
66 | 66 | |
|
67 | 67 | ret = { |
|
68 | 68 | 'msg': ( |
|
69 | 69 | 'Granted perm: `%s` (recursive:%s) for user group: `%s`' |
|
70 | 70 | ' in repo group: `%s`' % ( |
|
71 | 71 | perm, apply_to_children, user_group.users_group_name, |
|
72 | 72 | repo_group.name |
|
73 | 73 | ) |
|
74 | 74 | ), |
|
75 | 75 | 'success': True |
|
76 | 76 | } |
|
77 | 77 | expected = ret |
|
78 | 78 | try: |
|
79 | 79 | assert_ok(id_, expected, given=response.body) |
|
80 | 80 | finally: |
|
81 | 81 | RepoGroupModel().revoke_user_group_permission( |
|
82 | 82 | repo_group.group_id, user_group.users_group_id) |
|
83 | 83 | |
|
84 | 84 | @pytest.mark.parametrize( |
|
85 | 85 | "name, perm, apply_to_children, grant_admin, access_ok", [ |
|
86 | 86 | ('none_fails', 'group.none', 'none', False, False), |
|
87 | 87 | ('read_fails', 'group.read', 'none', False, False), |
|
88 | 88 | ('write_fails', 'group.write', 'none', False, False), |
|
89 | 89 | ('admin_fails', 'group.admin', 'none', False, False), |
|
90 | 90 | |
|
91 | 91 | # with granted perms |
|
92 | 92 | ('none_ok', 'group.none', 'none', True, True), |
|
93 | 93 | ('read_ok', 'group.read', 'none', True, True), |
|
94 | 94 | ('write_ok', 'group.write', 'none', True, True), |
|
95 | 95 | ('admin_ok', 'group.admin', 'none', True, True), |
|
96 | 96 | ] |
|
97 | 97 | ) |
|
98 | 98 | def test_api_grant_user_group_permission_to_repo_group_by_regular_user( |
|
99 | 99 | self, name, perm, apply_to_children, grant_admin, access_ok, |
|
100 | 100 | user_util): |
|
101 | 101 | user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
102 | 102 | user_group = user_util.create_user_group() |
|
103 | 103 | repo_group = user_util.create_repo_group() |
|
104 | 104 | if grant_admin: |
|
105 | 105 | user_util.grant_user_permission_to_repo_group( |
|
106 | 106 | repo_group, user, 'group.admin') |
|
107 | 107 | |
|
108 | 108 | id_, params = build_data( |
|
109 | 109 | self.apikey_regular, |
|
110 | 110 | 'grant_user_group_permission_to_repo_group', |
|
111 | 111 | repogroupid=repo_group.name, |
|
112 | 112 | usergroupid=user_group.users_group_name, |
|
113 | 113 | perm=perm, |
|
114 | 114 | apply_to_children=apply_to_children,) |
|
115 | 115 | response = api_call(self.app, params) |
|
116 | 116 | if access_ok: |
|
117 | 117 | ret = { |
|
118 | 118 | 'msg': ( |
|
119 | 119 | 'Granted perm: `%s` (recursive:%s) for user group: `%s`' |
|
120 | 120 | ' in repo group: `%s`' % ( |
|
121 | 121 | perm, apply_to_children, user_group.users_group_name, |
|
122 | 122 | repo_group.name |
|
123 | 123 | ) |
|
124 | 124 | ), |
|
125 | 125 | 'success': True |
|
126 | 126 | } |
|
127 | 127 | expected = ret |
|
128 | 128 | try: |
|
129 | 129 | assert_ok(id_, expected, given=response.body) |
|
130 | 130 | finally: |
|
131 | 131 | RepoGroupModel().revoke_user_group_permission( |
|
132 | 132 | repo_group.group_id, user_group.users_group_id) |
|
133 | 133 | else: |
|
134 | 134 | expected = 'repository group `%s` does not exist' % (repo_group.name,) |
|
135 | 135 | assert_error(id_, expected, given=response.body) |
|
136 | 136 | |
|
137 | 137 | def test_api_grant_user_group_permission_to_repo_group_wrong_permission( |
|
138 | 138 | self, user_util): |
|
139 | 139 | user_group = user_util.create_user_group() |
|
140 | 140 | repo_group = user_util.create_repo_group() |
|
141 | 141 | perm = 'haha.no.permission' |
|
142 | 142 | id_, params = build_data( |
|
143 | 143 | self.apikey, |
|
144 | 144 | 'grant_user_group_permission_to_repo_group', |
|
145 | 145 | repogroupid=repo_group.name, |
|
146 | 146 | usergroupid=user_group.users_group_name, |
|
147 | 147 | perm=perm) |
|
148 | 148 | response = api_call(self.app, params) |
|
149 | 149 | |
|
150 | 150 | expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,) |
|
151 | 151 | assert_error(id_, expected, given=response.body) |
|
152 | 152 | |
|
153 | 153 | @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash) |
|
154 | 154 | def test_api_grant_user_group_permission_exception_when_adding_2( |
|
155 | 155 | self, user_util): |
|
156 | 156 | user_group = user_util.create_user_group() |
|
157 | 157 | repo_group = user_util.create_repo_group() |
|
158 | 158 | perm = 'group.read' |
|
159 | 159 | id_, params = build_data( |
|
160 | 160 | self.apikey, |
|
161 | 161 | 'grant_user_group_permission_to_repo_group', |
|
162 | 162 | repogroupid=repo_group.name, |
|
163 | 163 | usergroupid=user_group.users_group_name, |
|
164 | 164 | perm=perm) |
|
165 | 165 | response = api_call(self.app, params) |
|
166 | 166 | |
|
167 | 167 | expected = ( |
|
168 | 168 | 'failed to edit permission for user group: `%s`' |
|
169 | 169 | ' in repo group: `%s`' % ( |
|
170 | 170 | user_group.users_group_name, repo_group.name) |
|
171 | 171 | ) |
|
172 | 172 | assert_error(id_, expected, given=response.body) |
@@ -1,96 +1,96 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.user_group import UserGroupModel |
|
23 | 23 | from rhodecode.api.tests.utils import ( |
|
24 | 24 | build_data, api_call, assert_ok, assert_error) |
|
25 | 25 | |
|
26 | 26 | |
|
27 | 27 | @pytest.mark.usefixtures("testuser_api", "app") |
|
28 | 28 | class TestGrantUserGroupPermissionFromUserGroup(object): |
|
29 | 29 | @pytest.mark.parametrize("name, perm", [ |
|
30 | 30 | ('none', 'usergroup.none'), |
|
31 | 31 | ('read', 'usergroup.read'), |
|
32 | 32 | ('write', 'usergroup.write'), |
|
33 | 33 | ('admin', 'usergroup.admin'), |
|
34 | 34 | |
|
35 | 35 | ('none', 'usergroup.none'), |
|
36 | 36 | ('read', 'usergroup.read'), |
|
37 | 37 | ('write', 'usergroup.write'), |
|
38 | 38 | ('admin', 'usergroup.admin'), |
|
39 | 39 | |
|
40 | 40 | ('none', 'usergroup.none'), |
|
41 | 41 | ('read', 'usergroup.read'), |
|
42 | 42 | ('write', 'usergroup.write'), |
|
43 | 43 | ('admin', 'usergroup.admin'), |
|
44 | 44 | |
|
45 | 45 | ('none', 'usergroup.none'), |
|
46 | 46 | ('read', 'usergroup.read'), |
|
47 | 47 | ('write', 'usergroup.write'), |
|
48 | 48 | ('admin', 'usergroup.admin'), |
|
49 | 49 | ]) |
|
50 | 50 | def test_api_grant_user_group_permission_to_user_group( |
|
51 | 51 | self, name, perm, user_util): |
|
52 | 52 | group = user_util.create_user_group() |
|
53 | 53 | target_group = user_util.create_user_group() |
|
54 | 54 | |
|
55 | 55 | id_, params = build_data( |
|
56 | 56 | self.apikey, |
|
57 | 57 | 'grant_user_group_permission_to_user_group', |
|
58 | 58 | usergroupid=target_group.users_group_name, |
|
59 | 59 | sourceusergroupid=group.users_group_name, |
|
60 | 60 | perm=perm) |
|
61 | 61 | response = api_call(self.app, params) |
|
62 | 62 | |
|
63 | 63 | expected = { |
|
64 | 64 | 'msg': ( |
|
65 | 65 | 'Granted perm: `%s` for user group: `%s`' |
|
66 | 66 | ' in user group: `%s`' % ( |
|
67 | 67 | perm, group.users_group_name, |
|
68 | 68 | target_group.users_group_name |
|
69 | 69 | ) |
|
70 | 70 | ), |
|
71 | 71 | 'success': True |
|
72 | 72 | } |
|
73 | 73 | try: |
|
74 | 74 | assert_ok(id_, expected, given=response.body) |
|
75 | 75 | finally: |
|
76 | 76 | UserGroupModel().revoke_user_group_permission( |
|
77 | 77 | target_group.users_group_id, group.users_group_id) |
|
78 | 78 | |
|
79 | 79 | def test_api_grant_user_group_permission_to_user_group_same_failure( |
|
80 | 80 | self, user_util): |
|
81 | 81 | group = user_util.create_user_group() |
|
82 | 82 | |
|
83 | 83 | id_, params = build_data( |
|
84 | 84 | self.apikey, |
|
85 | 85 | 'grant_user_group_permission_to_user_group', |
|
86 | 86 | usergroupid=group.users_group_name, |
|
87 | 87 | sourceusergroupid=group.users_group_name, |
|
88 | 88 | perm='usergroup.none') |
|
89 | 89 | response = api_call(self.app, params) |
|
90 | 90 | |
|
91 | 91 | expected = ( |
|
92 | 92 | 'failed to edit permission for user group: `%s`' |
|
93 | 93 | ' in user group: `%s`' % ( |
|
94 | 94 | group.users_group_name, group.users_group_name) |
|
95 | 95 | ) |
|
96 | 96 | assert_error(id_, expected, given=response.body) |
@@ -1,86 +1,86 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo import RepoModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestGrantUserPermission(object): |
|
30 | 30 | @pytest.mark.parametrize("name, perm", [ |
|
31 | 31 | ('none', 'repository.none'), |
|
32 | 32 | ('read', 'repository.read'), |
|
33 | 33 | ('write', 'repository.write'), |
|
34 | 34 | ('admin', 'repository.admin') |
|
35 | 35 | ]) |
|
36 | 36 | def test_api_grant_user_permission(self, name, perm, backend, user_util): |
|
37 | 37 | user = user_util.create_user() |
|
38 | 38 | id_, params = build_data( |
|
39 | 39 | self.apikey, |
|
40 | 40 | 'grant_user_permission', |
|
41 | 41 | repoid=backend.repo_name, |
|
42 | 42 | userid=user.username, |
|
43 | 43 | perm=perm) |
|
44 | 44 | response = api_call(self.app, params) |
|
45 | 45 | |
|
46 | 46 | ret = { |
|
47 | 47 | 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % ( |
|
48 | 48 | perm, user.username, backend.repo_name |
|
49 | 49 | ), |
|
50 | 50 | 'success': True |
|
51 | 51 | } |
|
52 | 52 | expected = ret |
|
53 | 53 | assert_ok(id_, expected, given=response.body) |
|
54 | 54 | |
|
55 | 55 | def test_api_grant_user_permission_wrong_permission( |
|
56 | 56 | self, backend, user_util): |
|
57 | 57 | user = user_util.create_user() |
|
58 | 58 | perm = 'haha.no.permission' |
|
59 | 59 | id_, params = build_data( |
|
60 | 60 | self.apikey, |
|
61 | 61 | 'grant_user_permission', |
|
62 | 62 | repoid=backend.repo_name, |
|
63 | 63 | userid=user.username, |
|
64 | 64 | perm=perm) |
|
65 | 65 | response = api_call(self.app, params) |
|
66 | 66 | |
|
67 | 67 | expected = 'permission `%s` does not exist.' % (perm,) |
|
68 | 68 | assert_error(id_, expected, given=response.body) |
|
69 | 69 | |
|
70 | 70 | @mock.patch.object(RepoModel, 'grant_user_permission', crash) |
|
71 | 71 | def test_api_grant_user_permission_exception_when_adding( |
|
72 | 72 | self, backend, user_util): |
|
73 | 73 | user = user_util.create_user() |
|
74 | 74 | perm = 'repository.read' |
|
75 | 75 | id_, params = build_data( |
|
76 | 76 | self.apikey, |
|
77 | 77 | 'grant_user_permission', |
|
78 | 78 | repoid=backend.repo_name, |
|
79 | 79 | userid=user.username, |
|
80 | 80 | perm=perm) |
|
81 | 81 | response = api_call(self.app, params) |
|
82 | 82 | |
|
83 | 83 | expected = 'failed to edit permission for user: `%s` in repo: `%s`' % ( |
|
84 | 84 | user.username, backend.repo_name |
|
85 | 85 | ) |
|
86 | 86 | assert_error(id_, expected, given=response.body) |
@@ -1,156 +1,156 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.user import UserModel |
|
24 | 24 | from rhodecode.model.repo_group import RepoGroupModel |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok, crash) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestGrantUserPermissionFromRepoGroup(object): |
|
31 | 31 | @pytest.mark.parametrize("name, perm, apply_to_children", [ |
|
32 | 32 | ('none', 'group.none', 'none'), |
|
33 | 33 | ('read', 'group.read', 'none'), |
|
34 | 34 | ('write', 'group.write', 'none'), |
|
35 | 35 | ('admin', 'group.admin', 'none'), |
|
36 | 36 | |
|
37 | 37 | ('none', 'group.none', 'all'), |
|
38 | 38 | ('read', 'group.read', 'all'), |
|
39 | 39 | ('write', 'group.write', 'all'), |
|
40 | 40 | ('admin', 'group.admin', 'all'), |
|
41 | 41 | |
|
42 | 42 | ('none', 'group.none', 'repos'), |
|
43 | 43 | ('read', 'group.read', 'repos'), |
|
44 | 44 | ('write', 'group.write', 'repos'), |
|
45 | 45 | ('admin', 'group.admin', 'repos'), |
|
46 | 46 | |
|
47 | 47 | ('none', 'group.none', 'groups'), |
|
48 | 48 | ('read', 'group.read', 'groups'), |
|
49 | 49 | ('write', 'group.write', 'groups'), |
|
50 | 50 | ('admin', 'group.admin', 'groups'), |
|
51 | 51 | ]) |
|
52 | 52 | def test_api_grant_user_permission_to_repo_group( |
|
53 | 53 | self, name, perm, apply_to_children, user_util): |
|
54 | 54 | user = user_util.create_user() |
|
55 | 55 | repo_group = user_util.create_repo_group() |
|
56 | 56 | id_, params = build_data( |
|
57 | 57 | self.apikey, 'grant_user_permission_to_repo_group', |
|
58 | 58 | repogroupid=repo_group.name, userid=user.username, |
|
59 | 59 | perm=perm, apply_to_children=apply_to_children) |
|
60 | 60 | response = api_call(self.app, params) |
|
61 | 61 | |
|
62 | 62 | ret = { |
|
63 | 63 | 'msg': ( |
|
64 | 64 | 'Granted perm: `%s` (recursive:%s) for user: `%s`' |
|
65 | 65 | ' in repo group: `%s`' % ( |
|
66 | 66 | perm, apply_to_children, user.username, repo_group.name |
|
67 | 67 | ) |
|
68 | 68 | ), |
|
69 | 69 | 'success': True |
|
70 | 70 | } |
|
71 | 71 | expected = ret |
|
72 | 72 | assert_ok(id_, expected, given=response.body) |
|
73 | 73 | |
|
74 | 74 | @pytest.mark.parametrize( |
|
75 | 75 | "name, perm, apply_to_children, grant_admin, access_ok", [ |
|
76 | 76 | ('none_fails', 'group.none', 'none', False, False), |
|
77 | 77 | ('read_fails', 'group.read', 'none', False, False), |
|
78 | 78 | ('write_fails', 'group.write', 'none', False, False), |
|
79 | 79 | ('admin_fails', 'group.admin', 'none', False, False), |
|
80 | 80 | |
|
81 | 81 | # with granted perms |
|
82 | 82 | ('none_ok', 'group.none', 'none', True, True), |
|
83 | 83 | ('read_ok', 'group.read', 'none', True, True), |
|
84 | 84 | ('write_ok', 'group.write', 'none', True, True), |
|
85 | 85 | ('admin_ok', 'group.admin', 'none', True, True), |
|
86 | 86 | ] |
|
87 | 87 | ) |
|
88 | 88 | def test_api_grant_user_permission_to_repo_group_by_regular_user( |
|
89 | 89 | self, name, perm, apply_to_children, grant_admin, access_ok, |
|
90 | 90 | user_util): |
|
91 | 91 | user = user_util.create_user() |
|
92 | 92 | repo_group = user_util.create_repo_group() |
|
93 | 93 | |
|
94 | 94 | if grant_admin: |
|
95 | 95 | test_user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
96 | 96 | user_util.grant_user_permission_to_repo_group( |
|
97 | 97 | repo_group, test_user, 'group.admin') |
|
98 | 98 | |
|
99 | 99 | id_, params = build_data( |
|
100 | 100 | self.apikey_regular, 'grant_user_permission_to_repo_group', |
|
101 | 101 | repogroupid=repo_group.name, userid=user.username, |
|
102 | 102 | perm=perm, apply_to_children=apply_to_children) |
|
103 | 103 | response = api_call(self.app, params) |
|
104 | 104 | if access_ok: |
|
105 | 105 | ret = { |
|
106 | 106 | 'msg': ( |
|
107 | 107 | 'Granted perm: `%s` (recursive:%s) for user: `%s`' |
|
108 | 108 | ' in repo group: `%s`' % ( |
|
109 | 109 | perm, apply_to_children, user.username, repo_group.name |
|
110 | 110 | ) |
|
111 | 111 | ), |
|
112 | 112 | 'success': True |
|
113 | 113 | } |
|
114 | 114 | expected = ret |
|
115 | 115 | assert_ok(id_, expected, given=response.body) |
|
116 | 116 | else: |
|
117 | 117 | expected = 'repository group `%s` does not exist' % ( |
|
118 | 118 | repo_group.name, ) |
|
119 | 119 | assert_error(id_, expected, given=response.body) |
|
120 | 120 | |
|
121 | 121 | def test_api_grant_user_permission_to_repo_group_wrong_permission( |
|
122 | 122 | self, user_util): |
|
123 | 123 | user = user_util.create_user() |
|
124 | 124 | repo_group = user_util.create_repo_group() |
|
125 | 125 | perm = 'haha.no.permission' |
|
126 | 126 | id_, params = build_data( |
|
127 | 127 | self.apikey, |
|
128 | 128 | 'grant_user_permission_to_repo_group', |
|
129 | 129 | repogroupid=repo_group.name, |
|
130 | 130 | userid=user.username, |
|
131 | 131 | perm=perm) |
|
132 | 132 | response = api_call(self.app, params) |
|
133 | 133 | |
|
134 | 134 | expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,) |
|
135 | 135 | assert_error(id_, expected, given=response.body) |
|
136 | 136 | |
|
137 | 137 | @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash) |
|
138 | 138 | def test_api_grant_user_permission_to_repo_group_exception_when_adding( |
|
139 | 139 | self, user_util): |
|
140 | 140 | user = user_util.create_user() |
|
141 | 141 | repo_group = user_util.create_repo_group() |
|
142 | 142 | perm = 'group.read' |
|
143 | 143 | id_, params = build_data( |
|
144 | 144 | self.apikey, |
|
145 | 145 | 'grant_user_permission_to_repo_group', |
|
146 | 146 | repogroupid=repo_group.name, |
|
147 | 147 | userid=user.username, |
|
148 | 148 | perm=perm) |
|
149 | 149 | response = api_call(self.app, params) |
|
150 | 150 | |
|
151 | 151 | expected = ( |
|
152 | 152 | 'failed to edit permission for user: `%s` in repo group: `%s`' % ( |
|
153 | 153 | user.username, repo_group.name |
|
154 | 154 | ) |
|
155 | 155 | ) |
|
156 | 156 | assert_error(id_, expected, given=response.body) |
@@ -1,155 +1,155 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.user import UserModel |
|
24 | 24 | from rhodecode.model.user_group import UserGroupModel |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok, crash) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestGrantUserPermissionFromUserGroup(object): |
|
31 | 31 | @pytest.mark.parametrize("name, perm", [ |
|
32 | 32 | ('none', 'usergroup.none'), |
|
33 | 33 | ('read', 'usergroup.read'), |
|
34 | 34 | ('write', 'usergroup.write'), |
|
35 | 35 | ('admin', 'usergroup.admin'), |
|
36 | 36 | |
|
37 | 37 | ('none', 'usergroup.none'), |
|
38 | 38 | ('read', 'usergroup.read'), |
|
39 | 39 | ('write', 'usergroup.write'), |
|
40 | 40 | ('admin', 'usergroup.admin'), |
|
41 | 41 | |
|
42 | 42 | ('none', 'usergroup.none'), |
|
43 | 43 | ('read', 'usergroup.read'), |
|
44 | 44 | ('write', 'usergroup.write'), |
|
45 | 45 | ('admin', 'usergroup.admin'), |
|
46 | 46 | |
|
47 | 47 | ('none', 'usergroup.none'), |
|
48 | 48 | ('read', 'usergroup.read'), |
|
49 | 49 | ('write', 'usergroup.write'), |
|
50 | 50 | ('admin', 'usergroup.admin'), |
|
51 | 51 | ]) |
|
52 | 52 | def test_api_grant_user_permission_to_user_group( |
|
53 | 53 | self, name, perm, user_util): |
|
54 | 54 | user = user_util.create_user() |
|
55 | 55 | group = user_util.create_user_group() |
|
56 | 56 | id_, params = build_data( |
|
57 | 57 | self.apikey, |
|
58 | 58 | 'grant_user_permission_to_user_group', |
|
59 | 59 | usergroupid=group.users_group_name, |
|
60 | 60 | userid=user.username, |
|
61 | 61 | perm=perm) |
|
62 | 62 | response = api_call(self.app, params) |
|
63 | 63 | |
|
64 | 64 | ret = { |
|
65 | 65 | 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % ( |
|
66 | 66 | perm, user.username, group.users_group_name |
|
67 | 67 | ), |
|
68 | 68 | 'success': True |
|
69 | 69 | } |
|
70 | 70 | expected = ret |
|
71 | 71 | assert_ok(id_, expected, given=response.body) |
|
72 | 72 | |
|
73 | 73 | @pytest.mark.parametrize("name, perm, grant_admin, access_ok", [ |
|
74 | 74 | ('none_fails', 'usergroup.none', False, False), |
|
75 | 75 | ('read_fails', 'usergroup.read', False, False), |
|
76 | 76 | ('write_fails', 'usergroup.write', False, False), |
|
77 | 77 | ('admin_fails', 'usergroup.admin', False, False), |
|
78 | 78 | |
|
79 | 79 | # with granted perms |
|
80 | 80 | ('none_ok', 'usergroup.none', True, True), |
|
81 | 81 | ('read_ok', 'usergroup.read', True, True), |
|
82 | 82 | ('write_ok', 'usergroup.write', True, True), |
|
83 | 83 | ('admin_ok', 'usergroup.admin', True, True), |
|
84 | 84 | ]) |
|
85 | 85 | def test_api_grant_user_permission_to_user_group_by_regular_user( |
|
86 | 86 | self, name, perm, grant_admin, access_ok, user_util): |
|
87 | 87 | api_user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
88 | 88 | user = user_util.create_user() |
|
89 | 89 | group = user_util.create_user_group() |
|
90 | 90 | # grant the user ability to at least read the group |
|
91 | 91 | permission = 'usergroup.admin' if grant_admin else 'usergroup.read' |
|
92 | 92 | user_util.grant_user_permission_to_user_group( |
|
93 | 93 | group, api_user, permission) |
|
94 | 94 | |
|
95 | 95 | id_, params = build_data( |
|
96 | 96 | self.apikey_regular, |
|
97 | 97 | 'grant_user_permission_to_user_group', |
|
98 | 98 | usergroupid=group.users_group_name, |
|
99 | 99 | userid=user.username, |
|
100 | 100 | perm=perm) |
|
101 | 101 | response = api_call(self.app, params) |
|
102 | 102 | |
|
103 | 103 | if access_ok: |
|
104 | 104 | ret = { |
|
105 | 105 | 'msg': ( |
|
106 | 106 | 'Granted perm: `%s` for user: `%s` in user group: `%s`' % ( |
|
107 | 107 | perm, user.username, group.users_group_name |
|
108 | 108 | ) |
|
109 | 109 | ), |
|
110 | 110 | 'success': True |
|
111 | 111 | } |
|
112 | 112 | expected = ret |
|
113 | 113 | assert_ok(id_, expected, given=response.body) |
|
114 | 114 | else: |
|
115 | 115 | expected = 'user group `%s` does not exist' % ( |
|
116 | 116 | group.users_group_name) |
|
117 | 117 | assert_error(id_, expected, given=response.body) |
|
118 | 118 | |
|
119 | 119 | def test_api_grant_user_permission_to_user_group_wrong_permission( |
|
120 | 120 | self, user_util): |
|
121 | 121 | user = user_util.create_user() |
|
122 | 122 | group = user_util.create_user_group() |
|
123 | 123 | perm = 'haha.no.permission' |
|
124 | 124 | id_, params = build_data( |
|
125 | 125 | self.apikey, |
|
126 | 126 | 'grant_user_permission_to_user_group', |
|
127 | 127 | usergroupid=group.users_group_name, |
|
128 | 128 | userid=user.username, |
|
129 | 129 | perm=perm) |
|
130 | 130 | response = api_call(self.app, params) |
|
131 | 131 | |
|
132 | 132 | expected = 'permission `%s` does not exist. Permission should start with prefix: `usergroup.`' % perm |
|
133 | 133 | assert_error(id_, expected, given=response.body) |
|
134 | 134 | |
|
135 | 135 | def test_api_grant_user_permission_to_user_group_exception_when_adding( |
|
136 | 136 | self, user_util): |
|
137 | 137 | user = user_util.create_user() |
|
138 | 138 | group = user_util.create_user_group() |
|
139 | 139 | |
|
140 | 140 | perm = 'usergroup.read' |
|
141 | 141 | id_, params = build_data( |
|
142 | 142 | self.apikey, |
|
143 | 143 | 'grant_user_permission_to_user_group', |
|
144 | 144 | usergroupid=group.users_group_name, |
|
145 | 145 | userid=user.username, |
|
146 | 146 | perm=perm) |
|
147 | 147 | with mock.patch.object(UserGroupModel, 'grant_user_permission', crash): |
|
148 | 148 | response = api_call(self.app, params) |
|
149 | 149 | |
|
150 | 150 | expected = ( |
|
151 | 151 | 'failed to edit permission for user: `%s` in user group: `%s`' % ( |
|
152 | 152 | user.username, group.users_group_name |
|
153 | 153 | ) |
|
154 | 154 | ) |
|
155 | 155 | assert_error(id_, expected, given=response.body) |
@@ -1,67 +1,67 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.scm import ScmModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_ok, assert_error, crash) |
|
26 | 26 | from rhodecode.model.repo import RepoModel |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestInvalidateCache(object): |
|
31 | 31 | |
|
32 | 32 | def _set_cache(self, repo_name): |
|
33 | 33 | repo = RepoModel().get_by_repo_name(repo_name) |
|
34 | 34 | repo.scm_instance(cache=True) |
|
35 | 35 | |
|
36 | 36 | def test_api_invalidate_cache(self, backend): |
|
37 | 37 | self._set_cache(backend.repo_name) |
|
38 | 38 | |
|
39 | 39 | id_, params = build_data( |
|
40 | 40 | self.apikey, 'invalidate_cache', repoid=backend.repo_name) |
|
41 | 41 | response = api_call(self.app, params) |
|
42 | 42 | |
|
43 | 43 | expected = { |
|
44 | 44 | 'msg': "Cache for repository `%s` was invalidated" % ( |
|
45 | 45 | backend.repo_name,), |
|
46 | 46 | 'repository': backend.repo_name, |
|
47 | 47 | } |
|
48 | 48 | assert_ok(id_, expected, given=response.body) |
|
49 | 49 | |
|
50 | 50 | @mock.patch.object(ScmModel, 'mark_for_invalidation', crash) |
|
51 | 51 | def test_api_invalidate_cache_error(self, backend): |
|
52 | 52 | id_, params = build_data( |
|
53 | 53 | self.apikey, 'invalidate_cache', repoid=backend.repo_name) |
|
54 | 54 | response = api_call(self.app, params) |
|
55 | 55 | |
|
56 | 56 | expected = 'Error occurred during cache invalidation action' |
|
57 | 57 | assert_error(id_, expected, given=response.body) |
|
58 | 58 | |
|
59 | 59 | def test_api_invalidate_cache_regular_user_no_permission(self, backend): |
|
60 | 60 | self._set_cache(backend.repo_name) |
|
61 | 61 | |
|
62 | 62 | id_, params = build_data( |
|
63 | 63 | self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name) |
|
64 | 64 | response = api_call(self.app, params) |
|
65 | 65 | |
|
66 | 66 | expected = "repository `%s` does not exist" % (backend.repo_name,) |
|
67 | 67 | assert_error(id_, expected, given=response.body) |
@@ -1,258 +1,258 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.db import UserLog, PullRequest |
|
23 | 23 | from rhodecode.model.meta import Session |
|
24 | 24 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestMergePullRequest(object): |
|
31 | 31 | |
|
32 | 32 | @pytest.mark.backends("git", "hg") |
|
33 | 33 | def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications): |
|
34 | 34 | pull_request = pr_util.create_pull_request(mergeable=True) |
|
35 | 35 | pull_request_id = pull_request.pull_request_id |
|
36 | 36 | pull_request_repo = pull_request.target_repo.repo_name |
|
37 | 37 | |
|
38 | 38 | id_, params = build_data( |
|
39 | 39 | self.apikey, 'merge_pull_request', |
|
40 | 40 | repoid=pull_request_repo, |
|
41 | 41 | pullrequestid=pull_request_id) |
|
42 | 42 | |
|
43 | 43 | response = api_call(self.app, params) |
|
44 | 44 | |
|
45 | 45 | # The above api call detaches the pull request DB object from the |
|
46 | 46 | # session because of an unconditional transaction rollback in our |
|
47 | 47 | # middleware. Therefore we need to add it back here if we want to use it. |
|
48 | 48 | Session().add(pull_request) |
|
49 | 49 | |
|
50 | 50 | expected = 'merge not possible for following reasons: ' \ |
|
51 | 51 | 'Pull request reviewer approval is pending.' |
|
52 | 52 | assert_error(id_, expected, given=response.body) |
|
53 | 53 | |
|
54 | 54 | @pytest.mark.backends("git", "hg") |
|
55 | 55 | def test_api_merge_pull_request_merge_failed_disallowed_state( |
|
56 | 56 | self, pr_util, no_notifications): |
|
57 | 57 | pull_request = pr_util.create_pull_request(mergeable=True, approved=True) |
|
58 | 58 | pull_request_id = pull_request.pull_request_id |
|
59 | 59 | pull_request_repo = pull_request.target_repo.repo_name |
|
60 | 60 | |
|
61 | 61 | pr = PullRequest.get(pull_request_id) |
|
62 | 62 | pr.pull_request_state = pull_request.STATE_UPDATING |
|
63 | 63 | Session().add(pr) |
|
64 | 64 | Session().commit() |
|
65 | 65 | |
|
66 | 66 | id_, params = build_data( |
|
67 | 67 | self.apikey, 'merge_pull_request', |
|
68 | 68 | repoid=pull_request_repo, |
|
69 | 69 | pullrequestid=pull_request_id) |
|
70 | 70 | |
|
71 | 71 | response = api_call(self.app, params) |
|
72 | 72 | expected = 'Operation forbidden because pull request is in state {}, '\ |
|
73 | 73 | 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING, |
|
74 | 74 | PullRequest.STATE_CREATED) |
|
75 | 75 | assert_error(id_, expected, given=response.body) |
|
76 | 76 | |
|
77 | 77 | @pytest.mark.backends("git", "hg") |
|
78 | 78 | def test_api_merge_pull_request(self, pr_util, no_notifications): |
|
79 | 79 | pull_request = pr_util.create_pull_request(mergeable=True, approved=True) |
|
80 | 80 | author = pull_request.user_id |
|
81 | 81 | repo = pull_request.target_repo.repo_id |
|
82 | 82 | pull_request_id = pull_request.pull_request_id |
|
83 | 83 | pull_request_repo = pull_request.target_repo.repo_name |
|
84 | 84 | |
|
85 | 85 | id_, params = build_data( |
|
86 | 86 | self.apikey, 'comment_pull_request', |
|
87 | 87 | repoid=pull_request_repo, |
|
88 | 88 | pullrequestid=pull_request_id, |
|
89 | 89 | status='approved') |
|
90 | 90 | |
|
91 | 91 | response = api_call(self.app, params) |
|
92 | 92 | expected = { |
|
93 | 93 | 'comment_id': response.json.get('result', {}).get('comment_id'), |
|
94 | 94 | 'pull_request_id': pull_request_id, |
|
95 | 95 | 'status': {'given': 'approved', 'was_changed': True} |
|
96 | 96 | } |
|
97 | 97 | assert_ok(id_, expected, given=response.body) |
|
98 | 98 | |
|
99 | 99 | id_, params = build_data( |
|
100 | 100 | self.apikey, 'merge_pull_request', |
|
101 | 101 | repoid=pull_request_repo, |
|
102 | 102 | pullrequestid=pull_request_id) |
|
103 | 103 | |
|
104 | 104 | response = api_call(self.app, params) |
|
105 | 105 | |
|
106 | 106 | pull_request = PullRequest.get(pull_request_id) |
|
107 | 107 | |
|
108 | 108 | expected = { |
|
109 | 109 | 'executed': True, |
|
110 | 110 | 'failure_reason': 0, |
|
111 | 111 | 'merge_status_message': 'This pull request can be automatically merged.', |
|
112 | 112 | 'possible': True, |
|
113 | 113 | 'merge_commit_id': pull_request.shadow_merge_ref.commit_id, |
|
114 | 114 | 'merge_ref': pull_request.shadow_merge_ref.asdict() |
|
115 | 115 | } |
|
116 | 116 | |
|
117 | 117 | assert_ok(id_, expected, response.body) |
|
118 | 118 | |
|
119 | 119 | journal = UserLog.query()\ |
|
120 | 120 | .filter(UserLog.user_id == author)\ |
|
121 | 121 | .filter(UserLog.repository_id == repo) \ |
|
122 | 122 | .order_by(UserLog.user_log_id.asc()) \ |
|
123 | 123 | .all() |
|
124 | 124 | assert journal[-2].action == 'repo.pull_request.merge' |
|
125 | 125 | assert journal[-1].action == 'repo.pull_request.close' |
|
126 | 126 | |
|
127 | 127 | id_, params = build_data( |
|
128 | 128 | self.apikey, 'merge_pull_request', |
|
129 | 129 | repoid=pull_request_repo, pullrequestid=pull_request_id) |
|
130 | 130 | response = api_call(self.app, params) |
|
131 | 131 | |
|
132 | 132 | expected = 'merge not possible for following reasons: This pull request is closed.' |
|
133 | 133 | assert_error(id_, expected, given=response.body) |
|
134 | 134 | |
|
135 | 135 | @pytest.mark.backends("git", "hg") |
|
136 | 136 | def test_api_merge_pull_request_as_another_user_no_perms_to_merge( |
|
137 | 137 | self, pr_util, no_notifications, user_util): |
|
138 | 138 | merge_user = user_util.create_user() |
|
139 | 139 | merge_user_id = merge_user.user_id |
|
140 | 140 | merge_user_username = merge_user.username |
|
141 | 141 | |
|
142 | 142 | pull_request = pr_util.create_pull_request(mergeable=True, approved=True) |
|
143 | 143 | |
|
144 | 144 | pull_request_id = pull_request.pull_request_id |
|
145 | 145 | pull_request_repo = pull_request.target_repo.repo_name |
|
146 | 146 | |
|
147 | 147 | id_, params = build_data( |
|
148 | 148 | self.apikey, 'comment_pull_request', |
|
149 | 149 | repoid=pull_request_repo, |
|
150 | 150 | pullrequestid=pull_request_id, |
|
151 | 151 | status='approved') |
|
152 | 152 | |
|
153 | 153 | response = api_call(self.app, params) |
|
154 | 154 | expected = { |
|
155 | 155 | 'comment_id': response.json.get('result', {}).get('comment_id'), |
|
156 | 156 | 'pull_request_id': pull_request_id, |
|
157 | 157 | 'status': {'given': 'approved', 'was_changed': True} |
|
158 | 158 | } |
|
159 | 159 | assert_ok(id_, expected, given=response.body) |
|
160 | 160 | id_, params = build_data( |
|
161 | 161 | self.apikey, 'merge_pull_request', |
|
162 | 162 | repoid=pull_request_repo, |
|
163 | 163 | pullrequestid=pull_request_id, |
|
164 | 164 | userid=merge_user_id |
|
165 | 165 | ) |
|
166 | 166 | |
|
167 | 167 | response = api_call(self.app, params) |
|
168 | 168 | expected = 'merge not possible for following reasons: User `{}` ' \ |
|
169 | 169 | 'not allowed to perform merge.'.format(merge_user_username) |
|
170 | 170 | assert_error(id_, expected, response.body) |
|
171 | 171 | |
|
172 | 172 | @pytest.mark.backends("git", "hg") |
|
173 | 173 | def test_api_merge_pull_request_as_another_user(self, pr_util, no_notifications, user_util): |
|
174 | 174 | merge_user = user_util.create_user() |
|
175 | 175 | merge_user_id = merge_user.user_id |
|
176 | 176 | pull_request = pr_util.create_pull_request(mergeable=True, approved=True) |
|
177 | 177 | user_util.grant_user_permission_to_repo( |
|
178 | 178 | pull_request.target_repo, merge_user, 'repository.write') |
|
179 | 179 | author = pull_request.user_id |
|
180 | 180 | repo = pull_request.target_repo.repo_id |
|
181 | 181 | pull_request_id = pull_request.pull_request_id |
|
182 | 182 | pull_request_repo = pull_request.target_repo.repo_name |
|
183 | 183 | |
|
184 | 184 | id_, params = build_data( |
|
185 | 185 | self.apikey, 'comment_pull_request', |
|
186 | 186 | repoid=pull_request_repo, |
|
187 | 187 | pullrequestid=pull_request_id, |
|
188 | 188 | status='approved') |
|
189 | 189 | |
|
190 | 190 | response = api_call(self.app, params) |
|
191 | 191 | expected = { |
|
192 | 192 | 'comment_id': response.json.get('result', {}).get('comment_id'), |
|
193 | 193 | 'pull_request_id': pull_request_id, |
|
194 | 194 | 'status': {'given': 'approved', 'was_changed': True} |
|
195 | 195 | } |
|
196 | 196 | assert_ok(id_, expected, given=response.body) |
|
197 | 197 | |
|
198 | 198 | id_, params = build_data( |
|
199 | 199 | self.apikey, 'merge_pull_request', |
|
200 | 200 | repoid=pull_request_repo, |
|
201 | 201 | pullrequestid=pull_request_id, |
|
202 | 202 | userid=merge_user_id |
|
203 | 203 | ) |
|
204 | 204 | |
|
205 | 205 | response = api_call(self.app, params) |
|
206 | 206 | |
|
207 | 207 | pull_request = PullRequest.get(pull_request_id) |
|
208 | 208 | |
|
209 | 209 | expected = { |
|
210 | 210 | 'executed': True, |
|
211 | 211 | 'failure_reason': 0, |
|
212 | 212 | 'merge_status_message': 'This pull request can be automatically merged.', |
|
213 | 213 | 'possible': True, |
|
214 | 214 | 'merge_commit_id': pull_request.shadow_merge_ref.commit_id, |
|
215 | 215 | 'merge_ref': pull_request.shadow_merge_ref.asdict() |
|
216 | 216 | } |
|
217 | 217 | |
|
218 | 218 | assert_ok(id_, expected, response.body) |
|
219 | 219 | |
|
220 | 220 | journal = UserLog.query() \ |
|
221 | 221 | .filter(UserLog.user_id == merge_user_id) \ |
|
222 | 222 | .filter(UserLog.repository_id == repo) \ |
|
223 | 223 | .order_by(UserLog.user_log_id.asc()) \ |
|
224 | 224 | .all() |
|
225 | 225 | assert journal[-2].action == 'repo.pull_request.merge' |
|
226 | 226 | assert journal[-1].action == 'repo.pull_request.close' |
|
227 | 227 | |
|
228 | 228 | id_, params = build_data( |
|
229 | 229 | self.apikey, 'merge_pull_request', |
|
230 | 230 | repoid=pull_request_repo, pullrequestid=pull_request_id, userid=merge_user_id) |
|
231 | 231 | response = api_call(self.app, params) |
|
232 | 232 | |
|
233 | 233 | expected = 'merge not possible for following reasons: This pull request is closed.' |
|
234 | 234 | assert_error(id_, expected, given=response.body) |
|
235 | 235 | |
|
236 | 236 | @pytest.mark.backends("git", "hg") |
|
237 | 237 | def test_api_merge_pull_request_repo_error(self, pr_util): |
|
238 | 238 | pull_request = pr_util.create_pull_request() |
|
239 | 239 | id_, params = build_data( |
|
240 | 240 | self.apikey, 'merge_pull_request', |
|
241 | 241 | repoid=666, pullrequestid=pull_request.pull_request_id) |
|
242 | 242 | response = api_call(self.app, params) |
|
243 | 243 | |
|
244 | 244 | expected = 'repository `666` does not exist' |
|
245 | 245 | assert_error(id_, expected, given=response.body) |
|
246 | 246 | |
|
247 | 247 | @pytest.mark.backends("git", "hg") |
|
248 | 248 | def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util): |
|
249 | 249 | pull_request = pr_util.create_pull_request(mergeable=True) |
|
250 | 250 | id_, params = build_data( |
|
251 | 251 | self.apikey_regular, 'merge_pull_request', |
|
252 | 252 | repoid=pull_request.target_repo.repo_name, |
|
253 | 253 | pullrequestid=pull_request.pull_request_id, |
|
254 | 254 | userid=TEST_USER_ADMIN_LOGIN) |
|
255 | 255 | response = api_call(self.app, params) |
|
256 | 256 | |
|
257 | 257 | expected = 'userid is not the same as your user' |
|
258 | 258 | assert_error(id_, expected, given=response.body) |
@@ -1,54 +1,54 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import os |
|
21 | 21 | import mock |
|
22 | 22 | import pytest |
|
23 | 23 | |
|
24 | 24 | from rhodecode.tests import TESTS_TMP_PATH |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_ok, assert_error) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestPull(object): |
|
31 | 31 | |
|
32 | 32 | @pytest.mark.backends("git", "hg") |
|
33 | 33 | def test_api_pull(self, backend): |
|
34 | 34 | r = backend.create_repo() |
|
35 | 35 | repo_name = r.repo_name |
|
36 | 36 | clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name) |
|
37 | 37 | r.clone_uri = clone_uri |
|
38 | 38 | |
|
39 | 39 | id_, params = build_data(self.apikey, 'pull', repoid=repo_name,) |
|
40 | 40 | with mock.patch('rhodecode.model.scm.url_validator'): |
|
41 | 41 | response = api_call(self.app, params) |
|
42 | 42 | msg = 'Pulled from url `%s` on repo `%s`' % ( |
|
43 | 43 | clone_uri, repo_name) |
|
44 | 44 | expected = {'msg': msg, |
|
45 | 45 | 'repository': repo_name} |
|
46 | 46 | assert_ok(id_, expected, given=response.body) |
|
47 | 47 | |
|
48 | 48 | def test_api_pull_error(self, backend): |
|
49 | 49 | id_, params = build_data( |
|
50 | 50 | self.apikey, 'pull', repoid=backend.repo_name) |
|
51 | 51 | response = api_call(self.app, params) |
|
52 | 52 | |
|
53 | 53 | expected = 'Unable to pull changes from `None`' |
|
54 | 54 | assert_error(id_, expected, given=response.body) |
@@ -1,65 +1,65 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.db import Repository, RepositoryField |
|
23 | 23 | from rhodecode.api.tests.utils import build_data, api_call, assert_ok |
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | @pytest.mark.usefixtures("testuser_api", "app") |
|
27 | 27 | class TestRemoveFieldFromRepo(object): |
|
28 | 28 | def test_api_remove_field_from_repo(self, backend): |
|
29 | 29 | repo = backend.create_repo() |
|
30 | 30 | repo_name = repo.repo_name |
|
31 | 31 | |
|
32 | 32 | id_, params = build_data( |
|
33 | 33 | self.apikey, 'add_field_to_repo', |
|
34 | 34 | repoid=repo_name, |
|
35 | 35 | key='extra_field', |
|
36 | 36 | label='extra_field_label', |
|
37 | 37 | description='extra_field_desc') |
|
38 | 38 | response = api_call(self.app, params) |
|
39 | 39 | expected = { |
|
40 | 40 | 'msg': 'Added new repository field `extra_field`', |
|
41 | 41 | 'success': True, |
|
42 | 42 | } |
|
43 | 43 | assert_ok(id_, expected, given=response.body) |
|
44 | 44 | |
|
45 | 45 | repo = Repository.get_by_repo_name(repo_name) |
|
46 | 46 | repo_field = RepositoryField.get_by_key_name('extra_field', repo) |
|
47 | 47 | _data = repo_field.get_dict() |
|
48 | 48 | assert _data['field_desc'] == 'extra_field_desc' |
|
49 | 49 | assert _data['field_key'] == 'extra_field' |
|
50 | 50 | assert _data['field_label'] == 'extra_field_label' |
|
51 | 51 | |
|
52 | 52 | id_, params = build_data( |
|
53 | 53 | self.apikey, 'remove_field_from_repo', |
|
54 | 54 | repoid=repo_name, |
|
55 | 55 | key='extra_field') |
|
56 | 56 | response = api_call(self.app, params) |
|
57 | 57 | expected = { |
|
58 | 58 | 'msg': 'Deleted repository field `extra_field`', |
|
59 | 59 | 'success': True, |
|
60 | 60 | } |
|
61 | 61 | assert_ok(id_, expected, given=response.body) |
|
62 | 62 | repo = Repository.get_by_repo_name(repo_name) |
|
63 | 63 | repo_field = RepositoryField.get_by_key_name('extra_field', repo) |
|
64 | 64 | |
|
65 | 65 | assert repo_field is None |
@@ -1,57 +1,57 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.user_group import UserGroupModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestRemoveUserFromUserGroup(object): |
|
30 | 30 | def test_api_remove_user_from_user_group(self, user_util): |
|
31 | 31 | user, group = user_util.create_user_with_group() |
|
32 | 32 | user_name = user.username |
|
33 | 33 | group_name = group.users_group_name |
|
34 | 34 | id_, params = build_data( |
|
35 | 35 | self.apikey, 'remove_user_from_user_group', |
|
36 | 36 | usergroupid=group_name, |
|
37 | 37 | userid=user.username) |
|
38 | 38 | response = api_call(self.app, params) |
|
39 | 39 | |
|
40 | 40 | expected = { |
|
41 | 41 | 'msg': 'removed member `%s` from user group `%s`' % ( |
|
42 | 42 | user_name, group_name |
|
43 | 43 | ), |
|
44 | 44 | 'success': True} |
|
45 | 45 | assert_ok(id_, expected, given=response.body) |
|
46 | 46 | |
|
47 | 47 | @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash) |
|
48 | 48 | def test_api_remove_user_from_user_group_exception_occurred( |
|
49 | 49 | self, user_util): |
|
50 | 50 | user, group = user_util.create_user_with_group() |
|
51 | 51 | id_, params = build_data( |
|
52 | 52 | self.apikey, 'remove_user_from_user_group', |
|
53 | 53 | usergroupid=group.users_group_name, userid=user.username) |
|
54 | 54 | response = api_call(self.app, params) |
|
55 | 55 | expected = 'failed to remove member from user group `%s`' % ( |
|
56 | 56 | group.users_group_name) |
|
57 | 57 | assert_error(id_, expected, given=response.body) |
@@ -1,183 +1,183 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import mock |
|
22 | 22 | import pytest |
|
23 | 23 | |
|
24 | 24 | from rhodecode.model.db import Repository |
|
25 | 25 | from rhodecode.model.user import UserModel |
|
26 | 26 | from rhodecode.lib.ext_json import json |
|
27 | 27 | from rhodecode.lib.utils2 import time_to_datetime |
|
28 | 28 | from rhodecode.api.tests.utils import ( |
|
29 | 29 | build_data, api_call, assert_ok, assert_error, crash) |
|
30 | 30 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
31 | 31 | |
|
32 | 32 | |
|
33 | 33 | @pytest.mark.usefixtures("testuser_api", "app") |
|
34 | 34 | class TestLock(object): |
|
35 | 35 | def test_api_lock_repo_lock_aquire(self, backend): |
|
36 | 36 | id_, params = build_data( |
|
37 | 37 | self.apikey, 'lock', |
|
38 | 38 | userid=TEST_USER_ADMIN_LOGIN, |
|
39 | 39 | repoid=backend.repo_name, |
|
40 | 40 | locked=True) |
|
41 | 41 | response = api_call(self.app, params) |
|
42 | 42 | expected = { |
|
43 | 43 | 'repo': backend.repo_name, 'locked': True, |
|
44 | 44 | 'locked_since': response.json['result']['locked_since'], |
|
45 | 45 | 'locked_by': TEST_USER_ADMIN_LOGIN, |
|
46 | 46 | 'lock_state_changed': True, |
|
47 | 47 | 'lock_reason': Repository.LOCK_API, |
|
48 | 48 | 'msg': ('User `%s` set lock state for repo `%s` to `%s`' |
|
49 | 49 | % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True)) |
|
50 | 50 | } |
|
51 | 51 | assert_ok(id_, expected, given=response.body) |
|
52 | 52 | |
|
53 | 53 | def test_repo_lock_aquire_by_non_admin(self, backend): |
|
54 | 54 | repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN) |
|
55 | 55 | repo_name = repo.repo_name |
|
56 | 56 | id_, params = build_data( |
|
57 | 57 | self.apikey_regular, 'lock', |
|
58 | 58 | repoid=repo_name, |
|
59 | 59 | locked=True) |
|
60 | 60 | response = api_call(self.app, params) |
|
61 | 61 | expected = { |
|
62 | 62 | 'repo': repo_name, |
|
63 | 63 | 'locked': True, |
|
64 | 64 | 'locked_since': response.json['result']['locked_since'], |
|
65 | 65 | 'locked_by': self.TEST_USER_LOGIN, |
|
66 | 66 | 'lock_state_changed': True, |
|
67 | 67 | 'lock_reason': Repository.LOCK_API, |
|
68 | 68 | 'msg': ('User `%s` set lock state for repo `%s` to `%s`' |
|
69 | 69 | % (self.TEST_USER_LOGIN, repo_name, True)) |
|
70 | 70 | } |
|
71 | 71 | assert_ok(id_, expected, given=response.body) |
|
72 | 72 | |
|
73 | 73 | def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend): |
|
74 | 74 | repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN) |
|
75 | 75 | repo_name = repo.repo_name |
|
76 | 76 | id_, params = build_data( |
|
77 | 77 | self.apikey_regular, 'lock', |
|
78 | 78 | userid=TEST_USER_ADMIN_LOGIN, |
|
79 | 79 | repoid=repo_name, |
|
80 | 80 | locked=True) |
|
81 | 81 | response = api_call(self.app, params) |
|
82 | 82 | expected = 'userid is not the same as your user' |
|
83 | 83 | assert_error(id_, expected, given=response.body) |
|
84 | 84 | |
|
85 | 85 | def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend): |
|
86 | 86 | id_, params = build_data( |
|
87 | 87 | self.apikey_regular, 'lock', |
|
88 | 88 | repoid=backend.repo_name, |
|
89 | 89 | locked=True) |
|
90 | 90 | response = api_call(self.app, params) |
|
91 | 91 | expected = 'repository `%s` does not exist' % (backend.repo_name, ) |
|
92 | 92 | assert_error(id_, expected, given=response.body) |
|
93 | 93 | |
|
94 | 94 | def test_api_lock_repo_lock_release(self, backend): |
|
95 | 95 | id_, params = build_data( |
|
96 | 96 | self.apikey, 'lock', |
|
97 | 97 | userid=TEST_USER_ADMIN_LOGIN, |
|
98 | 98 | repoid=backend.repo_name, |
|
99 | 99 | locked=False) |
|
100 | 100 | response = api_call(self.app, params) |
|
101 | 101 | expected = { |
|
102 | 102 | 'repo': backend.repo_name, |
|
103 | 103 | 'locked': False, |
|
104 | 104 | 'locked_since': None, |
|
105 | 105 | 'locked_by': TEST_USER_ADMIN_LOGIN, |
|
106 | 106 | 'lock_state_changed': True, |
|
107 | 107 | 'lock_reason': Repository.LOCK_API, |
|
108 | 108 | 'msg': ('User `%s` set lock state for repo `%s` to `%s`' |
|
109 | 109 | % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False)) |
|
110 | 110 | } |
|
111 | 111 | assert_ok(id_, expected, given=response.body) |
|
112 | 112 | |
|
113 | 113 | def test_api_lock_repo_lock_aquire_optional_userid(self, backend): |
|
114 | 114 | id_, params = build_data( |
|
115 | 115 | self.apikey, 'lock', |
|
116 | 116 | repoid=backend.repo_name, |
|
117 | 117 | locked=True) |
|
118 | 118 | response = api_call(self.app, params) |
|
119 | 119 | time_ = response.json['result']['locked_since'] |
|
120 | 120 | expected = { |
|
121 | 121 | 'repo': backend.repo_name, |
|
122 | 122 | 'locked': True, |
|
123 | 123 | 'locked_since': time_, |
|
124 | 124 | 'locked_by': TEST_USER_ADMIN_LOGIN, |
|
125 | 125 | 'lock_state_changed': True, |
|
126 | 126 | 'lock_reason': Repository.LOCK_API, |
|
127 | 127 | 'msg': ('User `%s` set lock state for repo `%s` to `%s`' |
|
128 | 128 | % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True)) |
|
129 | 129 | } |
|
130 | 130 | |
|
131 | 131 | assert_ok(id_, expected, given=response.body) |
|
132 | 132 | |
|
133 | 133 | def test_api_lock_repo_lock_optional_locked(self, backend): |
|
134 | 134 | # TODO: Provide a fixture locked_repository or similar |
|
135 | 135 | repo = Repository.get_by_repo_name(backend.repo_name) |
|
136 | 136 | user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
137 | 137 | Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API) |
|
138 | 138 | |
|
139 | 139 | id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name) |
|
140 | 140 | response = api_call(self.app, params) |
|
141 | 141 | time_ = response.json['result']['locked_since'] |
|
142 | 142 | expected = { |
|
143 | 143 | 'repo': backend.repo_name, |
|
144 | 144 | 'locked': True, |
|
145 | 145 | 'locked_since': time_, |
|
146 | 146 | 'locked_by': TEST_USER_ADMIN_LOGIN, |
|
147 | 147 | 'lock_state_changed': False, |
|
148 | 148 | 'lock_reason': Repository.LOCK_API, |
|
149 | 149 | 'msg': ('Repo `%s` locked by `%s` on `%s`.' |
|
150 | 150 | % (backend.repo_name, TEST_USER_ADMIN_LOGIN, |
|
151 | 151 | json.dumps(time_to_datetime(time_)))) |
|
152 | 152 | } |
|
153 | 153 | assert_ok(id_, expected, given=response.body) |
|
154 | 154 | |
|
155 | 155 | def test_api_lock_repo_lock_optional_not_locked(self, backend): |
|
156 | 156 | repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN) |
|
157 | 157 | repo_name = repo.repo_name |
|
158 | 158 | assert repo.locked == [None, None, None] |
|
159 | 159 | id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id) |
|
160 | 160 | response = api_call(self.app, params) |
|
161 | 161 | expected = { |
|
162 | 162 | 'repo': repo_name, |
|
163 | 163 | 'locked': False, |
|
164 | 164 | 'locked_since': None, |
|
165 | 165 | 'locked_by': None, |
|
166 | 166 | 'lock_state_changed': False, |
|
167 | 167 | 'lock_reason': None, |
|
168 | 168 | 'msg': ('Repo `%s` not locked.' % (repo_name,)) |
|
169 | 169 | } |
|
170 | 170 | assert_ok(id_, expected, given=response.body) |
|
171 | 171 | |
|
172 | 172 | @mock.patch.object(Repository, 'lock', crash) |
|
173 | 173 | def test_api_lock_error(self, backend): |
|
174 | 174 | id_, params = build_data( |
|
175 | 175 | self.apikey, 'lock', |
|
176 | 176 | userid=TEST_USER_ADMIN_LOGIN, |
|
177 | 177 | repoid=backend.repo_name, |
|
178 | 178 | locked=True) |
|
179 | 179 | response = api_call(self.app, params) |
|
180 | 180 | |
|
181 | 181 | expected = 'Error occurred locking repository `%s`' % ( |
|
182 | 182 | backend.repo_name,) |
|
183 | 183 | assert_error(id_, expected, given=response.body) |
@@ -1,43 +1,43 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.scm import ScmModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_ok, assert_error, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestRescanRepos(object): |
|
30 | 30 | def test_api_rescan_repos(self): |
|
31 | 31 | id_, params = build_data(self.apikey, 'rescan_repos') |
|
32 | 32 | response = api_call(self.app, params) |
|
33 | 33 | |
|
34 | 34 | expected = {'added': [], 'removed': []} |
|
35 | 35 | assert_ok(id_, expected, given=response.body) |
|
36 | 36 | |
|
37 | 37 | @mock.patch.object(ScmModel, 'repo_scan', crash) |
|
38 | 38 | def test_api_rescann_error(self): |
|
39 | 39 | id_, params = build_data(self.apikey, 'rescan_repos', ) |
|
40 | 40 | response = api_call(self.app, params) |
|
41 | 41 | |
|
42 | 42 | expected = 'Error occurred during rescan repositories action' |
|
43 | 43 | assert_error(id_, expected, given=response.body) |
@@ -1,66 +1,66 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo import RepoModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestRevokeUserGroupPermission(object): |
|
30 | 30 | def test_api_revoke_user_group_permission(self, backend, user_util): |
|
31 | 31 | repo = backend.create_repo() |
|
32 | 32 | user_group = user_util.create_user_group() |
|
33 | 33 | user_util.grant_user_group_permission_to_repo( |
|
34 | 34 | repo, user_group, 'repository.read') |
|
35 | 35 | id_, params = build_data( |
|
36 | 36 | self.apikey, |
|
37 | 37 | 'revoke_user_group_permission', |
|
38 | 38 | repoid=backend.repo_name, |
|
39 | 39 | usergroupid=user_group.users_group_name) |
|
40 | 40 | response = api_call(self.app, params) |
|
41 | 41 | |
|
42 | 42 | expected = { |
|
43 | 43 | 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % ( |
|
44 | 44 | user_group.users_group_name, backend.repo_name |
|
45 | 45 | ), |
|
46 | 46 | 'success': True |
|
47 | 47 | } |
|
48 | 48 | assert_ok(id_, expected, given=response.body) |
|
49 | 49 | |
|
50 | 50 | @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash) |
|
51 | 51 | def test_api_revoke_user_group_permission_exception_when_adding( |
|
52 | 52 | self, backend, user_util): |
|
53 | 53 | user_group = user_util.create_user_group() |
|
54 | 54 | id_, params = build_data( |
|
55 | 55 | self.apikey, |
|
56 | 56 | 'revoke_user_group_permission', |
|
57 | 57 | repoid=backend.repo_name, |
|
58 | 58 | usergroupid=user_group.users_group_name) |
|
59 | 59 | response = api_call(self.app, params) |
|
60 | 60 | |
|
61 | 61 | expected = ( |
|
62 | 62 | 'failed to edit permission for user group: `%s` in repo: `%s`' % ( |
|
63 | 63 | user_group.users_group_name, backend.repo_name |
|
64 | 64 | ) |
|
65 | 65 | ) |
|
66 | 66 | assert_error(id_, expected, given=response.body) |
@@ -1,128 +1,128 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo_group import RepoGroupModel |
|
24 | 24 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
25 | 25 | from rhodecode.api.tests.utils import ( |
|
26 | 26 | build_data, api_call, assert_error, assert_ok, crash) |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | @pytest.mark.usefixtures("testuser_api", "app") |
|
30 | 30 | class TestRevokeUserGroupPermissionFromRepoGroup(object): |
|
31 | 31 | @pytest.mark.parametrize("name, apply_to_children", [ |
|
32 | 32 | ('none', 'none'), |
|
33 | 33 | ('all', 'all'), |
|
34 | 34 | ('repos', 'repos'), |
|
35 | 35 | ('groups', 'groups'), |
|
36 | 36 | ]) |
|
37 | 37 | def test_api_revoke_user_group_permission_from_repo_group( |
|
38 | 38 | self, name, apply_to_children, user_util): |
|
39 | 39 | user_group = user_util.create_user_group() |
|
40 | 40 | repo_group = user_util.create_repo_group() |
|
41 | 41 | user_util.grant_user_group_permission_to_repo_group( |
|
42 | 42 | repo_group, user_group, 'group.read') |
|
43 | 43 | |
|
44 | 44 | id_, params = build_data( |
|
45 | 45 | self.apikey, 'revoke_user_group_permission_from_repo_group', |
|
46 | 46 | repogroupid=repo_group.name, |
|
47 | 47 | usergroupid=user_group.users_group_name, |
|
48 | 48 | apply_to_children=apply_to_children,) |
|
49 | 49 | response = api_call(self.app, params) |
|
50 | 50 | |
|
51 | 51 | expected = { |
|
52 | 52 | 'msg': ( |
|
53 | 53 | 'Revoked perm (recursive:%s) for user group: `%s`' |
|
54 | 54 | ' in repo group: `%s`' % ( |
|
55 | 55 | apply_to_children, user_group.users_group_name, |
|
56 | 56 | repo_group.name |
|
57 | 57 | ) |
|
58 | 58 | ), |
|
59 | 59 | 'success': True |
|
60 | 60 | } |
|
61 | 61 | assert_ok(id_, expected, given=response.body) |
|
62 | 62 | |
|
63 | 63 | @pytest.mark.parametrize( |
|
64 | 64 | "name, apply_to_children, grant_admin, access_ok", [ |
|
65 | 65 | ('none', 'none', False, False), |
|
66 | 66 | ('all', 'all', False, False), |
|
67 | 67 | ('repos', 'repos', False, False), |
|
68 | 68 | ('groups', 'groups', False, False), |
|
69 | 69 | |
|
70 | 70 | # after granting admin rights |
|
71 | 71 | ('none', 'none', False, False), |
|
72 | 72 | ('all', 'all', False, False), |
|
73 | 73 | ('repos', 'repos', False, False), |
|
74 | 74 | ('groups', 'groups', False, False), |
|
75 | 75 | ] |
|
76 | 76 | ) |
|
77 | 77 | def test_api_revoke_user_group_permission_from_repo_group_by_regular_user( |
|
78 | 78 | self, name, apply_to_children, grant_admin, access_ok, user_util): |
|
79 | 79 | user_group = user_util.create_user_group() |
|
80 | 80 | repo_group = user_util.create_repo_group() |
|
81 | 81 | user_util.grant_user_group_permission_to_repo_group( |
|
82 | 82 | repo_group, user_group, 'group.read') |
|
83 | 83 | |
|
84 | 84 | if grant_admin: |
|
85 | 85 | user_util.grant_user_permission_to_repo_group( |
|
86 | 86 | repo_group.name, self.TEST_USER_LOGIN, 'group.admin') |
|
87 | 87 | |
|
88 | 88 | id_, params = build_data( |
|
89 | 89 | self.apikey_regular, |
|
90 | 90 | 'revoke_user_group_permission_from_repo_group', |
|
91 | 91 | repogroupid=repo_group.name, |
|
92 | 92 | usergroupid=user_group.users_group_name, |
|
93 | 93 | apply_to_children=apply_to_children,) |
|
94 | 94 | response = api_call(self.app, params) |
|
95 | 95 | if access_ok: |
|
96 | 96 | expected = { |
|
97 | 97 | 'msg': ( |
|
98 | 98 | 'Revoked perm (recursive:%s) for user group: `%s`' |
|
99 | 99 | ' in repo group: `%s`' % ( |
|
100 | 100 | apply_to_children, TEST_USER_ADMIN_LOGIN, |
|
101 | 101 | repo_group.name |
|
102 | 102 | ) |
|
103 | 103 | ), |
|
104 | 104 | 'success': True |
|
105 | 105 | } |
|
106 | 106 | assert_ok(id_, expected, given=response.body) |
|
107 | 107 | else: |
|
108 | 108 | expected = 'repository group `%s` does not exist' % ( |
|
109 | 109 | repo_group.name,) |
|
110 | 110 | assert_error(id_, expected, given=response.body) |
|
111 | 111 | |
|
112 | 112 | @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash) |
|
113 | 113 | def test_api_revoke_user_group_permission_from_repo_group_exception_on_add( |
|
114 | 114 | self, user_util): |
|
115 | 115 | user_group = user_util.create_user_group() |
|
116 | 116 | repo_group = user_util.create_repo_group() |
|
117 | 117 | id_, params = build_data( |
|
118 | 118 | self.apikey, 'revoke_user_group_permission_from_repo_group', |
|
119 | 119 | repogroupid=repo_group.name, |
|
120 | 120 | usergroupid=user_group.users_group_name) |
|
121 | 121 | response = api_call(self.app, params) |
|
122 | 122 | |
|
123 | 123 | expected = ( |
|
124 | 124 | 'failed to edit permission for user group: `%s`' |
|
125 | 125 | ' in repo group: `%s`' % ( |
|
126 | 126 | user_group.users_group_name, repo_group.name) |
|
127 | 127 | ) |
|
128 | 128 | assert_error(id_, expected, given=response.body) |
@@ -1,57 +1,57 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.model.user import UserModel |
|
23 | 23 | from rhodecode.api.tests.utils import build_data, api_call, assert_ok |
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | @pytest.mark.usefixtures("testuser_api", "app") |
|
27 | 27 | class TestRevokeUserGroupPermissionFromUserGroup(object): |
|
28 | 28 | @pytest.mark.parametrize("name", [ |
|
29 | 29 | ('none',), |
|
30 | 30 | ('all',), |
|
31 | 31 | ('repos',), |
|
32 | 32 | ('groups',), |
|
33 | 33 | ]) |
|
34 | 34 | def test_api_revoke_user_group_permission_from_user_group( |
|
35 | 35 | self, name, user_util): |
|
36 | 36 | user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
37 | 37 | group = user_util.create_user_group() |
|
38 | 38 | source_group = user_util.create_user_group() |
|
39 | 39 | |
|
40 | 40 | user_util.grant_user_permission_to_user_group( |
|
41 | 41 | group, user, 'usergroup.read') |
|
42 | 42 | user_util.grant_user_group_permission_to_user_group( |
|
43 | 43 | source_group, group, 'usergroup.read') |
|
44 | 44 | |
|
45 | 45 | id_, params = build_data( |
|
46 | 46 | self.apikey, 'revoke_user_group_permission_from_user_group', |
|
47 | 47 | usergroupid=group.users_group_name, |
|
48 | 48 | sourceusergroupid=source_group.users_group_name) |
|
49 | 49 | response = api_call(self.app, params) |
|
50 | 50 | |
|
51 | 51 | expected = { |
|
52 | 52 | 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % ( |
|
53 | 53 | source_group.users_group_name, group.users_group_name |
|
54 | 54 | ), |
|
55 | 55 | 'success': True |
|
56 | 56 | } |
|
57 | 57 | assert_ok(id_, expected, given=response.body) |
@@ -1,65 +1,65 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo import RepoModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestRevokeUserPermission(object): |
|
30 | 30 | def test_api_revoke_user_permission(self, backend, user_util): |
|
31 | 31 | repo = backend.create_repo() |
|
32 | 32 | user = user_util.create_user() |
|
33 | 33 | user_util.grant_user_permission_to_repo( |
|
34 | 34 | repo, user, 'repository.read') |
|
35 | 35 | |
|
36 | 36 | id_, params = build_data( |
|
37 | 37 | self.apikey, |
|
38 | 38 | 'revoke_user_permission', |
|
39 | 39 | repoid=repo.repo_name, |
|
40 | 40 | userid=user.username) |
|
41 | 41 | response = api_call(self.app, params) |
|
42 | 42 | |
|
43 | 43 | expected = { |
|
44 | 44 | 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % ( |
|
45 | 45 | user.username, backend.repo_name |
|
46 | 46 | ), |
|
47 | 47 | 'success': True |
|
48 | 48 | } |
|
49 | 49 | assert_ok(id_, expected, given=response.body) |
|
50 | 50 | |
|
51 | 51 | @mock.patch.object(RepoModel, 'revoke_user_permission', crash) |
|
52 | 52 | def test_api_revoke_user_permission_exception_when_adding( |
|
53 | 53 | self, backend, user_util): |
|
54 | 54 | user = user_util.create_user() |
|
55 | 55 | id_, params = build_data( |
|
56 | 56 | self.apikey, |
|
57 | 57 | 'revoke_user_permission', |
|
58 | 58 | repoid=backend.repo_name, |
|
59 | 59 | userid=user.username) |
|
60 | 60 | response = api_call(self.app, params) |
|
61 | 61 | |
|
62 | 62 | expected = 'failed to edit permission for user: `%s` in repo: `%s`' % ( |
|
63 | 63 | user.username, backend.repo_name |
|
64 | 64 | ) |
|
65 | 65 | assert_error(id_, expected, given=response.body) |
@@ -1,125 +1,125 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo_group import RepoGroupModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestRevokeUserPermissionFromRepoGroup(object): |
|
30 | 30 | @pytest.mark.parametrize("name, apply_to_children", [ |
|
31 | 31 | ('none', 'none'), |
|
32 | 32 | ('all', 'all'), |
|
33 | 33 | ('repos', 'repos'), |
|
34 | 34 | ('groups', 'groups'), |
|
35 | 35 | ]) |
|
36 | 36 | def test_api_revoke_user_permission_from_repo_group( |
|
37 | 37 | self, name, apply_to_children, user_util): |
|
38 | 38 | user = user_util.create_user() |
|
39 | 39 | repo_group = user_util.create_repo_group() |
|
40 | 40 | user_util.grant_user_permission_to_repo_group( |
|
41 | 41 | repo_group, user, 'group.read') |
|
42 | 42 | |
|
43 | 43 | id_, params = build_data( |
|
44 | 44 | self.apikey, |
|
45 | 45 | 'revoke_user_permission_from_repo_group', |
|
46 | 46 | repogroupid=repo_group.name, |
|
47 | 47 | userid=user.username, |
|
48 | 48 | apply_to_children=apply_to_children,) |
|
49 | 49 | response = api_call(self.app, params) |
|
50 | 50 | |
|
51 | 51 | expected = { |
|
52 | 52 | 'msg': ( |
|
53 | 53 | 'Revoked perm (recursive:%s) for user: `%s`' |
|
54 | 54 | ' in repo group: `%s`' % ( |
|
55 | 55 | apply_to_children, user.username, repo_group.name |
|
56 | 56 | ) |
|
57 | 57 | ), |
|
58 | 58 | 'success': True |
|
59 | 59 | } |
|
60 | 60 | assert_ok(id_, expected, given=response.body) |
|
61 | 61 | |
|
62 | 62 | @pytest.mark.parametrize( |
|
63 | 63 | "name, apply_to_children, grant_admin, access_ok", [ |
|
64 | 64 | ('none', 'none', False, False), |
|
65 | 65 | ('all', 'all', False, False), |
|
66 | 66 | ('repos', 'repos', False, False), |
|
67 | 67 | ('groups', 'groups', False, False), |
|
68 | 68 | |
|
69 | 69 | # after granting admin rights |
|
70 | 70 | ('none', 'none', False, False), |
|
71 | 71 | ('all', 'all', False, False), |
|
72 | 72 | ('repos', 'repos', False, False), |
|
73 | 73 | ('groups', 'groups', False, False), |
|
74 | 74 | ] |
|
75 | 75 | ) |
|
76 | 76 | def test_api_revoke_user_permission_from_repo_group_by_regular_user( |
|
77 | 77 | self, name, apply_to_children, grant_admin, access_ok, user_util): |
|
78 | 78 | user = user_util.create_user() |
|
79 | 79 | repo_group = user_util.create_repo_group() |
|
80 | 80 | permission = 'group.admin' if grant_admin else 'group.read' |
|
81 | 81 | user_util.grant_user_permission_to_repo_group( |
|
82 | 82 | repo_group, user, permission) |
|
83 | 83 | |
|
84 | 84 | id_, params = build_data( |
|
85 | 85 | self.apikey_regular, |
|
86 | 86 | 'revoke_user_permission_from_repo_group', |
|
87 | 87 | repogroupid=repo_group.name, |
|
88 | 88 | userid=user.username, |
|
89 | 89 | apply_to_children=apply_to_children,) |
|
90 | 90 | response = api_call(self.app, params) |
|
91 | 91 | if access_ok: |
|
92 | 92 | expected = { |
|
93 | 93 | 'msg': ( |
|
94 | 94 | 'Revoked perm (recursive:%s) for user: `%s`' |
|
95 | 95 | ' in repo group: `%s`' % ( |
|
96 | 96 | apply_to_children, user.username, repo_group.name |
|
97 | 97 | ) |
|
98 | 98 | ), |
|
99 | 99 | 'success': True |
|
100 | 100 | } |
|
101 | 101 | assert_ok(id_, expected, given=response.body) |
|
102 | 102 | else: |
|
103 | 103 | expected = 'repository group `%s` does not exist' % ( |
|
104 | 104 | repo_group.name) |
|
105 | 105 | assert_error(id_, expected, given=response.body) |
|
106 | 106 | |
|
107 | 107 | @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash) |
|
108 | 108 | def test_api_revoke_user_permission_from_repo_group_exception_when_adding( |
|
109 | 109 | self, user_util): |
|
110 | 110 | user = user_util.create_user() |
|
111 | 111 | repo_group = user_util.create_repo_group() |
|
112 | 112 | id_, params = build_data( |
|
113 | 113 | self.apikey, |
|
114 | 114 | 'revoke_user_permission_from_repo_group', |
|
115 | 115 | repogroupid=repo_group.name, |
|
116 | 116 | userid=user.username |
|
117 | 117 | ) |
|
118 | 118 | response = api_call(self.app, params) |
|
119 | 119 | |
|
120 | 120 | expected = ( |
|
121 | 121 | 'failed to edit permission for user: `%s` in repo group: `%s`' % ( |
|
122 | 122 | user.username, repo_group.name |
|
123 | 123 | ) |
|
124 | 124 | ) |
|
125 | 125 | assert_error(id_, expected, given=response.body) |
@@ -1,111 +1,111 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.user_group import UserGroupModel |
|
24 | 24 | from rhodecode.api.tests.utils import ( |
|
25 | 25 | build_data, api_call, assert_error, assert_ok, crash) |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | @pytest.mark.usefixtures("testuser_api", "app") |
|
29 | 29 | class TestRevokeUserPermissionFromUserGroup(object): |
|
30 | 30 | @pytest.mark.parametrize("name", [ |
|
31 | 31 | ('none',), |
|
32 | 32 | ('all',), |
|
33 | 33 | ('repos',), |
|
34 | 34 | ('groups',), |
|
35 | 35 | ]) |
|
36 | 36 | def test_api_revoke_user_permission_from_user_group(self, name, user_util): |
|
37 | 37 | user = user_util.create_user() |
|
38 | 38 | group = user_util.create_user_group() |
|
39 | 39 | user_util.grant_user_permission_to_user_group( |
|
40 | 40 | group, user, 'usergroup.admin') |
|
41 | 41 | |
|
42 | 42 | id_, params = build_data( |
|
43 | 43 | self.apikey, |
|
44 | 44 | 'revoke_user_permission_from_user_group', |
|
45 | 45 | usergroupid=group.users_group_name, |
|
46 | 46 | userid=user.username) |
|
47 | 47 | response = api_call(self.app, params) |
|
48 | 48 | |
|
49 | 49 | expected = { |
|
50 | 50 | 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % ( |
|
51 | 51 | user.username, group.users_group_name |
|
52 | 52 | ), |
|
53 | 53 | 'success': True |
|
54 | 54 | } |
|
55 | 55 | assert_ok(id_, expected, given=response.body) |
|
56 | 56 | |
|
57 | 57 | @pytest.mark.parametrize("name, grant_admin, access_ok", [ |
|
58 | 58 | ('none', False, False), |
|
59 | 59 | ('all', False, False), |
|
60 | 60 | ('repos', False, False), |
|
61 | 61 | ('groups', False, False), |
|
62 | 62 | |
|
63 | 63 | # after granting admin rights |
|
64 | 64 | ('none', False, False), |
|
65 | 65 | ('all', False, False), |
|
66 | 66 | ('repos', False, False), |
|
67 | 67 | ('groups', False, False), |
|
68 | 68 | ]) |
|
69 | 69 | def test_api_revoke_user_permission_from_user_group_by_regular_user( |
|
70 | 70 | self, name, grant_admin, access_ok, user_util): |
|
71 | 71 | user = user_util.create_user() |
|
72 | 72 | group = user_util.create_user_group() |
|
73 | 73 | permission = 'usergroup.admin' if grant_admin else 'usergroup.read' |
|
74 | 74 | user_util.grant_user_permission_to_user_group(group, user, permission) |
|
75 | 75 | |
|
76 | 76 | id_, params = build_data( |
|
77 | 77 | self.apikey_regular, |
|
78 | 78 | 'revoke_user_permission_from_user_group', |
|
79 | 79 | usergroupid=group.users_group_name, |
|
80 | 80 | userid=user.username) |
|
81 | 81 | response = api_call(self.app, params) |
|
82 | 82 | if access_ok: |
|
83 | 83 | expected = { |
|
84 | 84 | 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % ( |
|
85 | 85 | user.username, group.users_group_name |
|
86 | 86 | ), |
|
87 | 87 | 'success': True |
|
88 | 88 | } |
|
89 | 89 | assert_ok(id_, expected, given=response.body) |
|
90 | 90 | else: |
|
91 | 91 | expected = 'user group `%s` does not exist' % ( |
|
92 | 92 | group.users_group_name) |
|
93 | 93 | assert_error(id_, expected, given=response.body) |
|
94 | 94 | |
|
95 | 95 | @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash) |
|
96 | 96 | def test_api_revoke_user_permission_from_user_group_exception_when_adding( |
|
97 | 97 | self, user_util): |
|
98 | 98 | user = user_util.create_user() |
|
99 | 99 | group = user_util.create_user_group() |
|
100 | 100 | id_, params = build_data( |
|
101 | 101 | self.apikey, |
|
102 | 102 | 'revoke_user_permission_from_user_group', |
|
103 | 103 | usergroupid=group.users_group_name, |
|
104 | 104 | userid=user.username) |
|
105 | 105 | response = api_call(self.app, params) |
|
106 | 106 | |
|
107 | 107 | expected = ( |
|
108 | 108 | 'failed to edit permission for user: `%s` in user group: `%s`' % ( |
|
109 | 109 | user.username, group.users_group_name) |
|
110 | 110 | ) |
|
111 | 111 | assert_error(id_, expected, given=response.body) |
@@ -1,58 +1,58 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.api.tests.utils import build_data, api_call, assert_ok, assert_error |
|
24 | 24 | |
|
25 | 25 | |
|
26 | 26 | @pytest.mark.usefixtures("testuser_api", "app") |
|
27 | 27 | class TestStoreException(object): |
|
28 | 28 | |
|
29 | 29 | def test_store_exception_invalid_json(self): |
|
30 | 30 | id_, params = build_data(self.apikey, 'store_exception', |
|
31 | 31 | exc_data_json='XXX,{') |
|
32 | 32 | response = api_call(self.app, params) |
|
33 | 33 | |
|
34 | 34 | expected = 'Failed to parse JSON data from exc_data_json field. ' \ |
|
35 | 35 | 'Please make sure it contains a valid JSON.' |
|
36 | 36 | assert_error(id_, expected, given=response.body) |
|
37 | 37 | |
|
38 | 38 | def test_store_exception_missing_json_params_json(self): |
|
39 | 39 | id_, params = build_data(self.apikey, 'store_exception', |
|
40 | 40 | exc_data_json='{"foo":"bar"}') |
|
41 | 41 | response = api_call(self.app, params) |
|
42 | 42 | |
|
43 | 43 | expected = "Missing exc_traceback, or exc_type_name in " \ |
|
44 | 44 | "exc_data_json field. Missing: 'exc_traceback'" |
|
45 | 45 | assert_error(id_, expected, given=response.body) |
|
46 | 46 | |
|
47 | 47 | def test_store_exception(self): |
|
48 | 48 | id_, params = build_data( |
|
49 | 49 | self.apikey, 'store_exception', |
|
50 | 50 | exc_data_json='{"exc_traceback": "invalid", "exc_type_name":"ValueError"}') |
|
51 | 51 | response = api_call(self.app, params) |
|
52 | 52 | exc_id = response.json['result']['exc_id'] |
|
53 | 53 | |
|
54 | 54 | expected = { |
|
55 | 55 | 'exc_id': exc_id, |
|
56 | 56 | 'exc_url': 'http://example.com/_admin/settings/exceptions/{}'.format(exc_id) |
|
57 | 57 | } |
|
58 | 58 | assert_ok(id_, expected, given=response.body) |
@@ -1,214 +1,214 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import pytest |
|
21 | 21 | |
|
22 | 22 | from rhodecode.lib.vcs.nodes import FileNode |
|
23 | 23 | from rhodecode.model.db import User |
|
24 | 24 | from rhodecode.model.pull_request import PullRequestModel |
|
25 | 25 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
26 | 26 | from rhodecode.api.tests.utils import ( |
|
27 | 27 | build_data, api_call, assert_ok, assert_error) |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
31 | 31 | class TestUpdatePullRequest(object): |
|
32 | 32 | |
|
33 | 33 | @pytest.mark.backends("git", "hg") |
|
34 | 34 | def test_api_update_pull_request_title_or_description( |
|
35 | 35 | self, pr_util, no_notifications): |
|
36 | 36 | pull_request = pr_util.create_pull_request() |
|
37 | 37 | |
|
38 | 38 | id_, params = build_data( |
|
39 | 39 | self.apikey, 'update_pull_request', |
|
40 | 40 | repoid=pull_request.target_repo.repo_name, |
|
41 | 41 | pullrequestid=pull_request.pull_request_id, |
|
42 | 42 | title='New TITLE OF A PR', |
|
43 | 43 | description='New DESC OF A PR', |
|
44 | 44 | ) |
|
45 | 45 | response = api_call(self.app, params) |
|
46 | 46 | |
|
47 | 47 | expected = { |
|
48 | 48 | "msg": "Updated pull request `{}`".format( |
|
49 | 49 | pull_request.pull_request_id), |
|
50 | 50 | "pull_request": response.json['result']['pull_request'], |
|
51 | 51 | "updated_commits": {"added": [], "common": [], "removed": []}, |
|
52 | 52 | "updated_reviewers": {"added": [], "removed": []}, |
|
53 | 53 | "updated_observers": {"added": [], "removed": []}, |
|
54 | 54 | } |
|
55 | 55 | |
|
56 | 56 | response_json = response.json['result'] |
|
57 | 57 | assert response_json == expected |
|
58 | 58 | pr = response_json['pull_request'] |
|
59 | 59 | assert pr['title'] == 'New TITLE OF A PR' |
|
60 | 60 | assert pr['description'] == 'New DESC OF A PR' |
|
61 | 61 | |
|
62 | 62 | @pytest.mark.backends("git", "hg") |
|
63 | 63 | def test_api_try_update_closed_pull_request( |
|
64 | 64 | self, pr_util, no_notifications): |
|
65 | 65 | pull_request = pr_util.create_pull_request() |
|
66 | 66 | PullRequestModel().close_pull_request( |
|
67 | 67 | pull_request, TEST_USER_ADMIN_LOGIN) |
|
68 | 68 | |
|
69 | 69 | id_, params = build_data( |
|
70 | 70 | self.apikey, 'update_pull_request', |
|
71 | 71 | repoid=pull_request.target_repo.repo_name, |
|
72 | 72 | pullrequestid=pull_request.pull_request_id) |
|
73 | 73 | response = api_call(self.app, params) |
|
74 | 74 | |
|
75 | 75 | expected = 'pull request `{}` update failed, pull request ' \ |
|
76 | 76 | 'is closed'.format(pull_request.pull_request_id) |
|
77 | 77 | |
|
78 | 78 | assert_error(id_, expected, response.body) |
|
79 | 79 | |
|
80 | 80 | @pytest.mark.backends("git", "hg") |
|
81 | 81 | def test_api_update_update_commits(self, pr_util, no_notifications): |
|
82 | 82 | commits = [ |
|
83 | 83 | {'message': 'a'}, |
|
84 | 84 | {'message': 'b', 'added': [FileNode(b'file_b', b'test_content\n')]}, |
|
85 | 85 | {'message': 'c', 'added': [FileNode(b'file_c', b'test_content\n')]}, |
|
86 | 86 | ] |
|
87 | 87 | pull_request = pr_util.create_pull_request( |
|
88 | 88 | commits=commits, target_head='a', source_head='b', revisions=['b']) |
|
89 | 89 | pr_util.update_source_repository(head='c') |
|
90 | 90 | repo = pull_request.source_repo.scm_instance() |
|
91 | 91 | commits = [x for x in repo.get_commits()] |
|
92 | 92 | |
|
93 | 93 | added_commit_id = commits[-1].raw_id # c commit |
|
94 | 94 | common_commit_id = commits[1].raw_id # b commit is common ancestor |
|
95 | 95 | total_commits = [added_commit_id, common_commit_id] |
|
96 | 96 | |
|
97 | 97 | id_, params = build_data( |
|
98 | 98 | self.apikey, 'update_pull_request', |
|
99 | 99 | repoid=pull_request.target_repo.repo_name, |
|
100 | 100 | pullrequestid=pull_request.pull_request_id, |
|
101 | 101 | update_commits=True |
|
102 | 102 | ) |
|
103 | 103 | response = api_call(self.app, params) |
|
104 | 104 | |
|
105 | 105 | expected = { |
|
106 | 106 | "msg": "Updated pull request `{}`".format( |
|
107 | 107 | pull_request.pull_request_id), |
|
108 | 108 | "pull_request": response.json['result']['pull_request'], |
|
109 | 109 | "updated_commits": {"added": [added_commit_id], |
|
110 | 110 | "common": [common_commit_id], |
|
111 | 111 | "total": total_commits, |
|
112 | 112 | "removed": []}, |
|
113 | 113 | "updated_reviewers": {"added": [], "removed": []}, |
|
114 | 114 | "updated_observers": {"added": [], "removed": []}, |
|
115 | 115 | } |
|
116 | 116 | |
|
117 | 117 | assert_ok(id_, expected, response.body) |
|
118 | 118 | |
|
119 | 119 | @pytest.mark.backends("git", "hg") |
|
120 | 120 | def test_api_update_change_reviewers( |
|
121 | 121 | self, user_util, pr_util, no_notifications): |
|
122 | 122 | a = user_util.create_user() |
|
123 | 123 | b = user_util.create_user() |
|
124 | 124 | c = user_util.create_user() |
|
125 | 125 | new_reviewers = [ |
|
126 | 126 | {'username': b.username, 'reasons': ['updated via API'], |
|
127 | 127 | 'mandatory':False}, |
|
128 | 128 | {'username': c.username, 'reasons': ['updated via API'], |
|
129 | 129 | 'mandatory':False}, |
|
130 | 130 | ] |
|
131 | 131 | |
|
132 | 132 | added = [b.username, c.username] |
|
133 | 133 | removed = [a.username] |
|
134 | 134 | |
|
135 | 135 | pull_request = pr_util.create_pull_request( |
|
136 | 136 | reviewers=[(a.username, ['added via API'], False, 'reviewer', [])]) |
|
137 | 137 | |
|
138 | 138 | id_, params = build_data( |
|
139 | 139 | self.apikey, 'update_pull_request', |
|
140 | 140 | repoid=pull_request.target_repo.repo_name, |
|
141 | 141 | pullrequestid=pull_request.pull_request_id, |
|
142 | 142 | reviewers=new_reviewers) |
|
143 | 143 | response = api_call(self.app, params) |
|
144 | 144 | expected = { |
|
145 | 145 | "msg": "Updated pull request `{}`".format( |
|
146 | 146 | pull_request.pull_request_id), |
|
147 | 147 | "pull_request": response.json['result']['pull_request'], |
|
148 | 148 | "updated_commits": {"added": [], "common": [], "removed": []}, |
|
149 | 149 | "updated_reviewers": {"added": added, "removed": removed}, |
|
150 | 150 | "updated_observers": {"added": [], "removed": []}, |
|
151 | 151 | } |
|
152 | 152 | |
|
153 | 153 | assert_ok(id_, expected, response.body) |
|
154 | 154 | |
|
155 | 155 | @pytest.mark.backends("git", "hg") |
|
156 | 156 | def test_api_update_bad_user_in_reviewers(self, pr_util): |
|
157 | 157 | pull_request = pr_util.create_pull_request() |
|
158 | 158 | |
|
159 | 159 | id_, params = build_data( |
|
160 | 160 | self.apikey, 'update_pull_request', |
|
161 | 161 | repoid=pull_request.target_repo.repo_name, |
|
162 | 162 | pullrequestid=pull_request.pull_request_id, |
|
163 | 163 | reviewers=[{'username': 'bad_name'}]) |
|
164 | 164 | response = api_call(self.app, params) |
|
165 | 165 | |
|
166 | 166 | expected = 'user `bad_name` does not exist' |
|
167 | 167 | |
|
168 | 168 | assert_error(id_, expected, response.body) |
|
169 | 169 | |
|
170 | 170 | @pytest.mark.backends("git", "hg") |
|
171 | 171 | def test_api_update_repo_error(self, pr_util): |
|
172 | 172 | pull_request = pr_util.create_pull_request() |
|
173 | 173 | id_, params = build_data( |
|
174 | 174 | self.apikey, 'update_pull_request', |
|
175 | 175 | repoid='fake', |
|
176 | 176 | pullrequestid=pull_request.pull_request_id, |
|
177 | 177 | reviewers=[{'username': 'bad_name'}]) |
|
178 | 178 | response = api_call(self.app, params) |
|
179 | 179 | |
|
180 | 180 | expected = 'repository `fake` does not exist' |
|
181 | 181 | |
|
182 | 182 | response_json = response.json['error'] |
|
183 | 183 | assert response_json == expected |
|
184 | 184 | |
|
185 | 185 | @pytest.mark.backends("git", "hg") |
|
186 | 186 | def test_api_update_pull_request_error(self, pr_util): |
|
187 | 187 | pull_request = pr_util.create_pull_request() |
|
188 | 188 | |
|
189 | 189 | id_, params = build_data( |
|
190 | 190 | self.apikey, 'update_pull_request', |
|
191 | 191 | repoid=pull_request.target_repo.repo_name, |
|
192 | 192 | pullrequestid=999999, |
|
193 | 193 | reviewers=[{'username': 'bad_name'}]) |
|
194 | 194 | response = api_call(self.app, params) |
|
195 | 195 | |
|
196 | 196 | expected = 'pull request `999999` does not exist' |
|
197 | 197 | assert_error(id_, expected, response.body) |
|
198 | 198 | |
|
199 | 199 | @pytest.mark.backends("git", "hg") |
|
200 | 200 | def test_api_update_pull_request_no_perms_to_update( |
|
201 | 201 | self, user_util, pr_util): |
|
202 | 202 | user = user_util.create_user() |
|
203 | 203 | pull_request = pr_util.create_pull_request() |
|
204 | 204 | |
|
205 | 205 | id_, params = build_data( |
|
206 | 206 | user.api_key, 'update_pull_request', |
|
207 | 207 | repoid=pull_request.target_repo.repo_name, |
|
208 | 208 | pullrequestid=pull_request.pull_request_id,) |
|
209 | 209 | response = api_call(self.app, params) |
|
210 | 210 | |
|
211 | 211 | expected = ('pull request `%s` update failed, ' |
|
212 | 212 | 'no permission to update.') % pull_request.pull_request_id |
|
213 | 213 | |
|
214 | 214 | assert_error(id_, expected, response.body) |
@@ -1,209 +1,209 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.repo import RepoModel |
|
24 | 24 | from rhodecode.model.scm import ScmModel |
|
25 | 25 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN |
|
26 | 26 | from rhodecode.api.tests.utils import ( |
|
27 | 27 | build_data, api_call, assert_error, assert_ok, crash, jsonify) |
|
28 | 28 | from rhodecode.tests.fixture import Fixture |
|
29 | 29 | from rhodecode.tests.fixture_mods.fixture_utils import plain_http_host_only_stub |
|
30 | 30 | |
|
31 | 31 | fixture = Fixture() |
|
32 | 32 | |
|
33 | 33 | UPDATE_REPO_NAME = 'api_update_me' |
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | class SAME_AS_UPDATES(object): |
|
37 | 37 | """ Constant used for tests below """ |
|
38 | 38 | |
|
39 | 39 | |
|
40 | 40 | @pytest.mark.usefixtures("testuser_api", "app") |
|
41 | 41 | class TestApiUpdateRepo(object): |
|
42 | 42 | |
|
43 | 43 | @pytest.mark.parametrize("updates, expected", [ |
|
44 | 44 | ({'owner': TEST_USER_REGULAR_LOGIN}, |
|
45 | 45 | SAME_AS_UPDATES), |
|
46 | 46 | |
|
47 | 47 | ({'description': 'new description'}, |
|
48 | 48 | SAME_AS_UPDATES), |
|
49 | 49 | |
|
50 | 50 | ({'clone_uri': 'http://foo.com/repo'}, |
|
51 | 51 | SAME_AS_UPDATES), |
|
52 | 52 | |
|
53 | 53 | ({'clone_uri': None}, |
|
54 | 54 | {'clone_uri': ''}), |
|
55 | 55 | |
|
56 | 56 | ({'clone_uri': ''}, |
|
57 | 57 | {'clone_uri': ''}), |
|
58 | 58 | |
|
59 | 59 | ({'clone_uri': 'http://example.com/repo_pull'}, |
|
60 | 60 | {'clone_uri': 'http://example.com/repo_pull'}), |
|
61 | 61 | |
|
62 | 62 | ({'push_uri': ''}, |
|
63 | 63 | {'push_uri': ''}), |
|
64 | 64 | |
|
65 | 65 | ({'push_uri': 'http://example.com/repo_push'}, |
|
66 | 66 | {'push_uri': 'http://example.com/repo_push'}), |
|
67 | 67 | |
|
68 | 68 | ({'landing_rev': None}, # auto-updated based on type of repo |
|
69 | 69 | {'landing_rev': [None, None]}), |
|
70 | 70 | |
|
71 | 71 | ({'enable_statistics': True}, |
|
72 | 72 | SAME_AS_UPDATES), |
|
73 | 73 | |
|
74 | 74 | ({'enable_locking': True}, |
|
75 | 75 | SAME_AS_UPDATES), |
|
76 | 76 | |
|
77 | 77 | ({'enable_downloads': True}, |
|
78 | 78 | SAME_AS_UPDATES), |
|
79 | 79 | |
|
80 | 80 | ({'repo_name': 'new_repo_name'}, |
|
81 | 81 | { |
|
82 | 82 | 'repo_name': 'new_repo_name', |
|
83 | 83 | 'url': 'http://{}/new_repo_name'.format(plain_http_host_only_stub()) |
|
84 | 84 | }), |
|
85 | 85 | |
|
86 | 86 | ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME), |
|
87 | 87 | '_group': 'test_group_for_update'}, |
|
88 | 88 | { |
|
89 | 89 | 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME), |
|
90 | 90 | 'url': 'http://{}/test_group_for_update/{}'.format( |
|
91 | 91 | plain_http_host_only_stub(), UPDATE_REPO_NAME) |
|
92 | 92 | }), |
|
93 | 93 | ]) |
|
94 | 94 | def test_api_update_repo(self, updates, expected, backend): |
|
95 | 95 | repo_name = UPDATE_REPO_NAME |
|
96 | 96 | repo = fixture.create_repo(repo_name, repo_type=backend.alias) |
|
97 | 97 | if updates.get('_group'): |
|
98 | 98 | fixture.create_repo_group(updates['_group']) |
|
99 | 99 | |
|
100 | 100 | if 'landing_rev' in updates: |
|
101 | 101 | default_landing_ref, _lbl = ScmModel.backend_landing_ref(backend.alias) |
|
102 | 102 | _type, _name = default_landing_ref.split(':') |
|
103 | 103 | updates['landing_rev'] = default_landing_ref |
|
104 | 104 | expected['landing_rev'] = [_type, _name] |
|
105 | 105 | |
|
106 | 106 | expected_api_data = repo.get_api_data(include_secrets=True) |
|
107 | 107 | if expected is SAME_AS_UPDATES: |
|
108 | 108 | expected_api_data.update(updates) |
|
109 | 109 | else: |
|
110 | 110 | expected_api_data.update(expected) |
|
111 | 111 | |
|
112 | 112 | id_, params = build_data( |
|
113 | 113 | self.apikey, 'update_repo', repoid=repo_name, **updates) |
|
114 | 114 | |
|
115 | 115 | with mock.patch('rhodecode.model.validation_schema.validators.url_validator'): |
|
116 | 116 | response = api_call(self.app, params) |
|
117 | 117 | |
|
118 | 118 | if updates.get('repo_name'): |
|
119 | 119 | repo_name = updates['repo_name'] |
|
120 | 120 | |
|
121 | 121 | try: |
|
122 | 122 | expected = { |
|
123 | 123 | 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name), |
|
124 | 124 | 'repository': jsonify(expected_api_data) |
|
125 | 125 | } |
|
126 | 126 | assert_ok(id_, expected, given=response.body) |
|
127 | 127 | finally: |
|
128 | 128 | fixture.destroy_repo(repo_name) |
|
129 | 129 | if updates.get('_group'): |
|
130 | 130 | fixture.destroy_repo_group(updates['_group']) |
|
131 | 131 | |
|
132 | 132 | def test_api_update_repo_fork_of_field(self, backend): |
|
133 | 133 | master_repo = backend.create_repo() |
|
134 | 134 | repo = backend.create_repo() |
|
135 | 135 | updates = { |
|
136 | 136 | 'fork_of': master_repo.repo_name, |
|
137 | 137 | 'fork_of_id': master_repo.repo_id |
|
138 | 138 | } |
|
139 | 139 | expected_api_data = repo.get_api_data(include_secrets=True) |
|
140 | 140 | expected_api_data.update(updates) |
|
141 | 141 | |
|
142 | 142 | id_, params = build_data( |
|
143 | 143 | self.apikey, 'update_repo', repoid=repo.repo_name, **updates) |
|
144 | 144 | response = api_call(self.app, params) |
|
145 | 145 | expected = { |
|
146 | 146 | 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name), |
|
147 | 147 | 'repository': jsonify(expected_api_data) |
|
148 | 148 | } |
|
149 | 149 | assert_ok(id_, expected, given=response.body) |
|
150 | 150 | result = response.json['result']['repository'] |
|
151 | 151 | assert result['fork_of'] == master_repo.repo_name |
|
152 | 152 | assert result['fork_of_id'] == master_repo.repo_id |
|
153 | 153 | |
|
154 | 154 | def test_api_update_repo_fork_of_not_found(self, backend): |
|
155 | 155 | master_repo_name = 'fake-parent-repo' |
|
156 | 156 | repo = backend.create_repo() |
|
157 | 157 | updates = { |
|
158 | 158 | 'fork_of': master_repo_name |
|
159 | 159 | } |
|
160 | 160 | id_, params = build_data( |
|
161 | 161 | self.apikey, 'update_repo', repoid=repo.repo_name, **updates) |
|
162 | 162 | response = api_call(self.app, params) |
|
163 | 163 | expected = { |
|
164 | 164 | 'repo_fork_of': 'Fork with id `{}` does not exists'.format( |
|
165 | 165 | master_repo_name)} |
|
166 | 166 | assert_error(id_, expected, given=response.body) |
|
167 | 167 | |
|
168 | 168 | def test_api_update_repo_with_repo_group_not_existing(self): |
|
169 | 169 | repo_name = 'admin_owned' |
|
170 | 170 | fake_repo_group = 'test_group_for_update' |
|
171 | 171 | fixture.create_repo(repo_name) |
|
172 | 172 | updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)} |
|
173 | 173 | id_, params = build_data( |
|
174 | 174 | self.apikey, 'update_repo', repoid=repo_name, **updates) |
|
175 | 175 | response = api_call(self.app, params) |
|
176 | 176 | try: |
|
177 | 177 | expected = { |
|
178 | 178 | 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group) |
|
179 | 179 | } |
|
180 | 180 | assert_error(id_, expected, given=response.body) |
|
181 | 181 | finally: |
|
182 | 182 | fixture.destroy_repo(repo_name) |
|
183 | 183 | |
|
184 | 184 | def test_api_update_repo_regular_user_not_allowed(self): |
|
185 | 185 | repo_name = 'admin_owned' |
|
186 | 186 | fixture.create_repo(repo_name) |
|
187 | 187 | updates = {'active': False} |
|
188 | 188 | id_, params = build_data( |
|
189 | 189 | self.apikey_regular, 'update_repo', repoid=repo_name, **updates) |
|
190 | 190 | response = api_call(self.app, params) |
|
191 | 191 | try: |
|
192 | 192 | expected = 'repository `%s` does not exist' % (repo_name,) |
|
193 | 193 | assert_error(id_, expected, given=response.body) |
|
194 | 194 | finally: |
|
195 | 195 | fixture.destroy_repo(repo_name) |
|
196 | 196 | |
|
197 | 197 | @mock.patch.object(RepoModel, 'update', crash) |
|
198 | 198 | def test_api_update_repo_exception_occurred(self, backend): |
|
199 | 199 | repo_name = UPDATE_REPO_NAME |
|
200 | 200 | fixture.create_repo(repo_name, repo_type=backend.alias) |
|
201 | 201 | id_, params = build_data( |
|
202 | 202 | self.apikey, 'update_repo', repoid=repo_name, |
|
203 | 203 | owner=TEST_USER_ADMIN_LOGIN,) |
|
204 | 204 | response = api_call(self.app, params) |
|
205 | 205 | try: |
|
206 | 206 | expected = 'failed to update repo `%s`' % (repo_name,) |
|
207 | 207 | assert_error(id_, expected, given=response.body) |
|
208 | 208 | finally: |
|
209 | 209 | fixture.destroy_repo(repo_name) |
@@ -1,149 +1,149 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import os |
|
21 | 21 | |
|
22 | 22 | import pytest |
|
23 | 23 | |
|
24 | 24 | from rhodecode.model.repo_group import RepoGroupModel |
|
25 | 25 | from rhodecode.model.user import UserModel |
|
26 | 26 | from rhodecode.api.tests.utils import ( |
|
27 | 27 | build_data, api_call, assert_error, assert_ok) |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
31 | 31 | class TestApiUpdateRepoGroup(object): |
|
32 | 32 | |
|
33 | 33 | def test_update_group_name(self, user_util): |
|
34 | 34 | new_group_name = 'new-group' |
|
35 | 35 | initial_name = self._update(user_util, group_name=new_group_name) |
|
36 | 36 | assert RepoGroupModel()._get_repo_group(initial_name) is None |
|
37 | 37 | new_group = RepoGroupModel()._get_repo_group(new_group_name) |
|
38 | 38 | assert new_group is not None |
|
39 | 39 | assert new_group.full_path == new_group_name |
|
40 | 40 | |
|
41 | 41 | def test_update_group_name_change_parent(self, user_util): |
|
42 | 42 | |
|
43 | 43 | parent_group = user_util.create_repo_group() |
|
44 | 44 | parent_group_name = parent_group.name |
|
45 | 45 | |
|
46 | 46 | expected_group_name = '{}/{}'.format(parent_group_name, 'new-group') |
|
47 | 47 | initial_name = self._update(user_util, group_name=expected_group_name) |
|
48 | 48 | |
|
49 | 49 | repo_group = RepoGroupModel()._get_repo_group(expected_group_name) |
|
50 | 50 | |
|
51 | 51 | assert repo_group is not None |
|
52 | 52 | assert repo_group.group_name == expected_group_name |
|
53 | 53 | assert repo_group.full_path == expected_group_name |
|
54 | 54 | assert RepoGroupModel()._get_repo_group(initial_name) is None |
|
55 | 55 | |
|
56 | 56 | new_path = os.path.join( |
|
57 | 57 | RepoGroupModel().repos_path, *repo_group.full_path_splitted) |
|
58 | 58 | assert os.path.exists(new_path) |
|
59 | 59 | |
|
60 | 60 | def test_update_enable_locking(self, user_util): |
|
61 | 61 | initial_name = self._update(user_util, enable_locking=True) |
|
62 | 62 | repo_group = RepoGroupModel()._get_repo_group(initial_name) |
|
63 | 63 | assert repo_group.enable_locking is True |
|
64 | 64 | |
|
65 | 65 | def test_update_description(self, user_util): |
|
66 | 66 | description = 'New description' |
|
67 | 67 | initial_name = self._update(user_util, description=description) |
|
68 | 68 | repo_group = RepoGroupModel()._get_repo_group(initial_name) |
|
69 | 69 | assert repo_group.group_description == description |
|
70 | 70 | |
|
71 | 71 | def test_update_owner(self, user_util): |
|
72 | 72 | owner = self.TEST_USER_LOGIN |
|
73 | 73 | initial_name = self._update(user_util, owner=owner) |
|
74 | 74 | repo_group = RepoGroupModel()._get_repo_group(initial_name) |
|
75 | 75 | assert repo_group.user.username == owner |
|
76 | 76 | |
|
77 | 77 | def test_update_group_name_conflict_with_existing(self, user_util): |
|
78 | 78 | group_1 = user_util.create_repo_group() |
|
79 | 79 | group_2 = user_util.create_repo_group() |
|
80 | 80 | repo_group_name_1 = group_1.group_name |
|
81 | 81 | repo_group_name_2 = group_2.group_name |
|
82 | 82 | |
|
83 | 83 | id_, params = build_data( |
|
84 | 84 | self.apikey, 'update_repo_group', repogroupid=repo_group_name_1, |
|
85 | 85 | group_name=repo_group_name_2) |
|
86 | 86 | response = api_call(self.app, params) |
|
87 | 87 | expected = { |
|
88 | 88 | 'unique_repo_group_name': |
|
89 | 89 | 'Repository group with name `{}` already exists'.format( |
|
90 | 90 | repo_group_name_2)} |
|
91 | 91 | assert_error(id_, expected, given=response.body) |
|
92 | 92 | |
|
93 | 93 | def test_api_update_repo_group_by_regular_user_no_permission(self, user_util): |
|
94 | 94 | temp_user = user_util.create_user() |
|
95 | 95 | temp_user_api_key = temp_user.api_key |
|
96 | 96 | parent_group = user_util.create_repo_group() |
|
97 | 97 | repo_group_name = parent_group.group_name |
|
98 | 98 | id_, params = build_data( |
|
99 | 99 | temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name) |
|
100 | 100 | response = api_call(self.app, params) |
|
101 | 101 | expected = 'repository group `%s` does not exist' % (repo_group_name,) |
|
102 | 102 | assert_error(id_, expected, given=response.body) |
|
103 | 103 | |
|
104 | 104 | def test_api_update_repo_group_regular_user_no_root_write_permissions( |
|
105 | 105 | self, user_util): |
|
106 | 106 | temp_user = user_util.create_user() |
|
107 | 107 | temp_user_api_key = temp_user.api_key |
|
108 | 108 | parent_group = user_util.create_repo_group(owner=temp_user.username) |
|
109 | 109 | repo_group_name = parent_group.group_name |
|
110 | 110 | |
|
111 | 111 | id_, params = build_data( |
|
112 | 112 | temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name, |
|
113 | 113 | group_name='at-root-level') |
|
114 | 114 | response = api_call(self.app, params) |
|
115 | 115 | expected = { |
|
116 | 116 | 'repo_group': 'You do not have the permission to store ' |
|
117 | 117 | 'repository groups in the root location.'} |
|
118 | 118 | assert_error(id_, expected, given=response.body) |
|
119 | 119 | |
|
120 | 120 | def _update(self, user_util, **kwargs): |
|
121 | 121 | repo_group = user_util.create_repo_group() |
|
122 | 122 | initial_name = repo_group.name |
|
123 | 123 | user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
124 | 124 | user_util.grant_user_permission_to_repo_group( |
|
125 | 125 | repo_group, user, 'group.admin') |
|
126 | 126 | |
|
127 | 127 | id_, params = build_data( |
|
128 | 128 | self.apikey, 'update_repo_group', repogroupid=initial_name, |
|
129 | 129 | **kwargs) |
|
130 | 130 | response = api_call(self.app, params) |
|
131 | 131 | |
|
132 | 132 | repo_group = RepoGroupModel.cls.get(repo_group.group_id) |
|
133 | 133 | |
|
134 | 134 | expected = { |
|
135 | 135 | 'msg': 'updated repository group ID:{} {}'.format( |
|
136 | 136 | repo_group.group_id, repo_group.group_name), |
|
137 | 137 | 'repo_group': { |
|
138 | 138 | 'repositories': [], |
|
139 | 139 | 'group_name': repo_group.group_name, |
|
140 | 140 | 'group_description': repo_group.group_description, |
|
141 | 141 | 'owner': repo_group.user.username, |
|
142 | 142 | 'group_id': repo_group.group_id, |
|
143 | 143 | 'parent_group': ( |
|
144 | 144 | repo_group.parent_group.name |
|
145 | 145 | if repo_group.parent_group else None) |
|
146 | 146 | } |
|
147 | 147 | } |
|
148 | 148 | assert_ok(id_, expected, given=response.body) |
|
149 | 149 | return initial_name |
@@ -1,120 +1,120 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.db import User |
|
24 | 24 | from rhodecode.model.user import UserModel |
|
25 | 25 | from rhodecode.tests import TEST_USER_ADMIN_LOGIN |
|
26 | 26 | from rhodecode.api.tests.utils import ( |
|
27 | 27 | build_data, api_call, assert_ok, assert_error, crash, jsonify) |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
31 | 31 | class TestUpdateUser(object): |
|
32 | 32 | @pytest.mark.parametrize("name, expected", [ |
|
33 | 33 | ('firstname', 'new_username'), |
|
34 | 34 | ('lastname', 'new_username'), |
|
35 | 35 | ('email', 'new_username'), |
|
36 | 36 | ('admin', True), |
|
37 | 37 | ('admin', False), |
|
38 | 38 | ('extern_type', 'ldap'), |
|
39 | 39 | ('extern_type', None), |
|
40 | 40 | ('extern_name', 'test'), |
|
41 | 41 | ('extern_name', None), |
|
42 | 42 | ('active', False), |
|
43 | 43 | ('active', True), |
|
44 | 44 | ('password', 'newpass'), |
|
45 | 45 | ('description', 'CTO 4 Life') |
|
46 | 46 | ]) |
|
47 | 47 | def test_api_update_user(self, name, expected, user_util): |
|
48 | 48 | usr = user_util.create_user() |
|
49 | 49 | |
|
50 | 50 | kw = {name: expected, 'userid': usr.user_id} |
|
51 | 51 | id_, params = build_data(self.apikey, 'update_user', **kw) |
|
52 | 52 | response = api_call(self.app, params) |
|
53 | 53 | |
|
54 | 54 | ret = { |
|
55 | 55 | 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username), |
|
56 | 56 | 'user': jsonify( |
|
57 | 57 | UserModel() |
|
58 | 58 | .get_by_username(usr.username) |
|
59 | 59 | .get_api_data(include_secrets=True) |
|
60 | 60 | ) |
|
61 | 61 | } |
|
62 | 62 | |
|
63 | 63 | expected = ret |
|
64 | 64 | assert_ok(id_, expected, given=response.body) |
|
65 | 65 | |
|
66 | 66 | def test_api_update_user_no_changed_params(self): |
|
67 | 67 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
68 | 68 | ret = jsonify(usr.get_api_data(include_secrets=True)) |
|
69 | 69 | id_, params = build_data( |
|
70 | 70 | self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN) |
|
71 | 71 | |
|
72 | 72 | response = api_call(self.app, params) |
|
73 | 73 | ret = { |
|
74 | 74 | 'msg': 'updated user ID:%s %s' % ( |
|
75 | 75 | usr.user_id, TEST_USER_ADMIN_LOGIN), |
|
76 | 76 | 'user': ret |
|
77 | 77 | } |
|
78 | 78 | expected = ret |
|
79 | 79 | expected['user']['last_activity'] = response.json['result']['user'][ |
|
80 | 80 | 'last_activity'] |
|
81 | 81 | assert_ok(id_, expected, given=response.body) |
|
82 | 82 | |
|
83 | 83 | def test_api_update_user_by_user_id(self): |
|
84 | 84 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
85 | 85 | ret = jsonify(usr.get_api_data(include_secrets=True)) |
|
86 | 86 | id_, params = build_data( |
|
87 | 87 | self.apikey, 'update_user', userid=usr.user_id) |
|
88 | 88 | |
|
89 | 89 | response = api_call(self.app, params) |
|
90 | 90 | ret = { |
|
91 | 91 | 'msg': 'updated user ID:%s %s' % ( |
|
92 | 92 | usr.user_id, TEST_USER_ADMIN_LOGIN), |
|
93 | 93 | 'user': ret |
|
94 | 94 | } |
|
95 | 95 | expected = ret |
|
96 | 96 | expected['user']['last_activity'] = response.json['result']['user'][ |
|
97 | 97 | 'last_activity'] |
|
98 | 98 | assert_ok(id_, expected, given=response.body) |
|
99 | 99 | |
|
100 | 100 | def test_api_update_user_default_user(self): |
|
101 | 101 | usr = User.get_default_user() |
|
102 | 102 | id_, params = build_data( |
|
103 | 103 | self.apikey, 'update_user', userid=usr.user_id) |
|
104 | 104 | |
|
105 | 105 | response = api_call(self.app, params) |
|
106 | 106 | expected = 'editing default user is forbidden' |
|
107 | 107 | assert_error(id_, expected, given=response.body) |
|
108 | 108 | |
|
109 | 109 | @mock.patch.object(UserModel, 'update_user', crash) |
|
110 | 110 | def test_api_update_user_when_exception_happens(self): |
|
111 | 111 | usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) |
|
112 | 112 | ret = jsonify(usr.get_api_data(include_secrets=True)) |
|
113 | 113 | id_, params = build_data( |
|
114 | 114 | self.apikey, 'update_user', userid=usr.user_id) |
|
115 | 115 | |
|
116 | 116 | response = api_call(self.app, params) |
|
117 | 117 | ret = 'failed to update user `%s`' % (usr.user_id,) |
|
118 | 118 | |
|
119 | 119 | expected = ret |
|
120 | 120 | assert_error(id_, expected, given=response.body) |
@@ -1,124 +1,124 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | import mock |
|
21 | 21 | import pytest |
|
22 | 22 | |
|
23 | 23 | from rhodecode.model.user import UserModel |
|
24 | 24 | from rhodecode.model.user_group import UserGroupModel |
|
25 | 25 | from rhodecode.tests import TEST_USER_ADMIN_EMAIL |
|
26 | 26 | from rhodecode.api.tests.utils import ( |
|
27 | 27 | build_data, api_call, assert_error, assert_ok, crash, jsonify) |
|
28 | 28 | |
|
29 | 29 | |
|
30 | 30 | @pytest.mark.usefixtures("testuser_api", "app") |
|
31 | 31 | class TestUpdateUserGroup(object): |
|
32 | 32 | @pytest.mark.parametrize("changing_attr, updates", [ |
|
33 | 33 | ('group_name', {'group_name': 'new_group_name'}), |
|
34 | 34 | ('group_name', {'group_name': 'test_group_for_update'}), |
|
35 | 35 | # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}), |
|
36 | 36 | ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}), |
|
37 | 37 | ('active', {'active': False}), |
|
38 | 38 | ('active', {'active': True}), |
|
39 | 39 | ('sync', {'sync': False}), |
|
40 | 40 | ('sync', {'sync': True}) |
|
41 | 41 | ]) |
|
42 | 42 | def test_api_update_user_group(self, changing_attr, updates, user_util): |
|
43 | 43 | user_group = user_util.create_user_group() |
|
44 | 44 | group_name = user_group.users_group_name |
|
45 | 45 | expected_api_data = user_group.get_api_data() |
|
46 | 46 | expected_api_data.update(updates) |
|
47 | 47 | |
|
48 | 48 | id_, params = build_data( |
|
49 | 49 | self.apikey, 'update_user_group', usergroupid=group_name, |
|
50 | 50 | **updates) |
|
51 | 51 | response = api_call(self.app, params) |
|
52 | 52 | |
|
53 | 53 | # special case for sync |
|
54 | 54 | if changing_attr == 'sync' and updates['sync'] is False: |
|
55 | 55 | expected_api_data['sync'] = None |
|
56 | 56 | elif changing_attr == 'sync' and updates['sync'] is True: |
|
57 | 57 | expected_api_data['sync'] = 'manual_api' |
|
58 | 58 | |
|
59 | 59 | expected = { |
|
60 | 60 | 'msg': 'updated user group ID:%s %s' % ( |
|
61 | 61 | user_group.users_group_id, user_group.users_group_name), |
|
62 | 62 | 'user_group': jsonify(expected_api_data) |
|
63 | 63 | } |
|
64 | 64 | assert_ok(id_, expected, given=response.body) |
|
65 | 65 | |
|
66 | 66 | @pytest.mark.parametrize("changing_attr, updates", [ |
|
67 | 67 | # TODO: mikhail: decide if we need to test against the commented params |
|
68 | 68 | # ('group_name', {'group_name': 'new_group_name'}), |
|
69 | 69 | # ('group_name', {'group_name': 'test_group_for_update'}), |
|
70 | 70 | # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}), |
|
71 | 71 | ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}), |
|
72 | 72 | ('active', {'active': False}), |
|
73 | 73 | ('active', {'active': True}), |
|
74 | 74 | ('sync', {'sync': False}), |
|
75 | 75 | ('sync', {'sync': True}) |
|
76 | 76 | ]) |
|
77 | 77 | def test_api_update_user_group_regular_user( |
|
78 | 78 | self, changing_attr, updates, user_util): |
|
79 | 79 | user_group = user_util.create_user_group() |
|
80 | 80 | group_name = user_group.users_group_name |
|
81 | 81 | expected_api_data = user_group.get_api_data() |
|
82 | 82 | expected_api_data.update(updates) |
|
83 | 83 | |
|
84 | 84 | # grant permission to this user |
|
85 | 85 | user = UserModel().get_by_username(self.TEST_USER_LOGIN) |
|
86 | 86 | |
|
87 | 87 | user_util.grant_user_permission_to_user_group( |
|
88 | 88 | user_group, user, 'usergroup.admin') |
|
89 | 89 | id_, params = build_data( |
|
90 | 90 | self.apikey_regular, 'update_user_group', |
|
91 | 91 | usergroupid=group_name, **updates) |
|
92 | 92 | response = api_call(self.app, params) |
|
93 | 93 | # special case for sync |
|
94 | 94 | if changing_attr == 'sync' and updates['sync'] is False: |
|
95 | 95 | expected_api_data['sync'] = None |
|
96 | 96 | elif changing_attr == 'sync' and updates['sync'] is True: |
|
97 | 97 | expected_api_data['sync'] = 'manual_api' |
|
98 | 98 | |
|
99 | 99 | expected = { |
|
100 | 100 | 'msg': 'updated user group ID:%s %s' % ( |
|
101 | 101 | user_group.users_group_id, user_group.users_group_name), |
|
102 | 102 | 'user_group': jsonify(expected_api_data) |
|
103 | 103 | } |
|
104 | 104 | assert_ok(id_, expected, given=response.body) |
|
105 | 105 | |
|
106 | 106 | def test_api_update_user_group_regular_user_no_permission(self, user_util): |
|
107 | 107 | user_group = user_util.create_user_group() |
|
108 | 108 | group_name = user_group.users_group_name |
|
109 | 109 | id_, params = build_data( |
|
110 | 110 | self.apikey_regular, 'update_user_group', usergroupid=group_name) |
|
111 | 111 | response = api_call(self.app, params) |
|
112 | 112 | |
|
113 | 113 | expected = 'user group `%s` does not exist' % (group_name) |
|
114 | 114 | assert_error(id_, expected, given=response.body) |
|
115 | 115 | |
|
116 | 116 | @mock.patch.object(UserGroupModel, 'update', crash) |
|
117 | 117 | def test_api_update_user_group_exception_occurred(self, user_util): |
|
118 | 118 | user_group = user_util.create_user_group() |
|
119 | 119 | group_name = user_group.users_group_name |
|
120 | 120 | id_, params = build_data( |
|
121 | 121 | self.apikey, 'update_user_group', usergroupid=group_name) |
|
122 | 122 | response = api_call(self.app, params) |
|
123 | 123 | expected = 'failed to update user group `%s`' % (group_name,) |
|
124 | 124 | assert_error(id_, expected, given=response.body) |
@@ -1,297 +1,297 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import pytest |
|
22 | 22 | from mock import Mock, patch |
|
23 | 23 | |
|
24 | 24 | from rhodecode.api import utils |
|
25 | 25 | from rhodecode.api import JSONRPCError |
|
26 | 26 | from rhodecode.lib.vcs.exceptions import RepositoryError |
|
27 | 27 | |
|
28 | 28 | |
|
29 | 29 | class TestGetCommitOrError(object): |
|
30 | 30 | |
|
31 | 31 | def setup_method(self): |
|
32 | 32 | self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10' |
|
33 | 33 | |
|
34 | 34 | @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d', 'branch:name']) |
|
35 | 35 | def test_ref_cannot_be_parsed(self, ref): |
|
36 | 36 | repo = Mock() |
|
37 | 37 | with pytest.raises(JSONRPCError) as excinfo: |
|
38 | 38 | utils.get_commit_or_error(ref, repo) |
|
39 | 39 | expected_message = ( |
|
40 | 40 | 'Ref `{ref}` given in a wrong format. Please check the API' |
|
41 | 41 | ' documentation for more details'.format(ref=ref) |
|
42 | 42 | ) |
|
43 | 43 | assert excinfo.value.message == expected_message |
|
44 | 44 | |
|
45 | 45 | def test_success_with_hash_specified(self): |
|
46 | 46 | repo = Mock() |
|
47 | 47 | ref_type = 'branch' |
|
48 | 48 | ref = '{}:master:{}'.format(ref_type, self.commit_hash) |
|
49 | 49 | |
|
50 | 50 | with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit: |
|
51 | 51 | result = utils.get_commit_or_error(ref, repo) |
|
52 | 52 | get_commit.assert_called_once_with( |
|
53 | 53 | repo, self.commit_hash) |
|
54 | 54 | assert result == get_commit() |
|
55 | 55 | |
|
56 | 56 | def test_raises_an_error_when_commit_not_found(self): |
|
57 | 57 | repo = Mock() |
|
58 | 58 | ref = 'branch:master:{}'.format(self.commit_hash) |
|
59 | 59 | |
|
60 | 60 | with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit: |
|
61 | 61 | get_commit.side_effect = RepositoryError('Commit not found') |
|
62 | 62 | with pytest.raises(JSONRPCError) as excinfo: |
|
63 | 63 | utils.get_commit_or_error(ref, repo) |
|
64 | 64 | expected_message = 'Ref `{}` does not exist'.format(ref) |
|
65 | 65 | assert excinfo.value.message == expected_message |
|
66 | 66 | |
|
67 | 67 | |
|
68 | 68 | class TestResolveRefOrError(object): |
|
69 | 69 | |
|
70 | 70 | def setup_method(self): |
|
71 | 71 | self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10' |
|
72 | 72 | |
|
73 | 73 | def test_success_with_no_hash_specified(self): |
|
74 | 74 | repo = Mock() |
|
75 | 75 | ref_type = 'branch' |
|
76 | 76 | ref_name = 'master' |
|
77 | 77 | ref = '{}:{}'.format(ref_type, ref_name) |
|
78 | 78 | |
|
79 | 79 | with patch('rhodecode.api.utils._get_ref_hash') \ |
|
80 | 80 | as _get_ref_hash: |
|
81 | 81 | _get_ref_hash.return_value = self.commit_hash |
|
82 | 82 | result = utils.resolve_ref_or_error(ref, repo) |
|
83 | 83 | _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name) |
|
84 | 84 | assert result == '{}:{}'.format(ref, self.commit_hash) |
|
85 | 85 | |
|
86 | 86 | def test_non_supported_refs(self): |
|
87 | 87 | repo = Mock() |
|
88 | 88 | ref = 'bookmark:ref' |
|
89 | 89 | with pytest.raises(JSONRPCError) as excinfo: |
|
90 | 90 | utils.resolve_ref_or_error(ref, repo) |
|
91 | 91 | expected_message = ( |
|
92 | 92 | 'The specified value:bookmark:`ref` does not exist, or is not allowed.') |
|
93 | 93 | assert excinfo.value.message == expected_message |
|
94 | 94 | |
|
95 | 95 | def test_branch_is_not_found(self): |
|
96 | 96 | repo = Mock() |
|
97 | 97 | ref = 'branch:non-existing-one' |
|
98 | 98 | with patch('rhodecode.api.utils._get_ref_hash')\ |
|
99 | 99 | as _get_ref_hash: |
|
100 | 100 | _get_ref_hash.side_effect = KeyError() |
|
101 | 101 | with pytest.raises(JSONRPCError) as excinfo: |
|
102 | 102 | utils.resolve_ref_or_error(ref, repo) |
|
103 | 103 | expected_message = ( |
|
104 | 104 | 'The specified value:branch:`non-existing-one` does not exist, or is not allowed.') |
|
105 | 105 | assert excinfo.value.message == expected_message |
|
106 | 106 | |
|
107 | 107 | def test_bookmark_is_not_found(self): |
|
108 | 108 | repo = Mock() |
|
109 | 109 | ref = 'bookmark:non-existing-one' |
|
110 | 110 | with patch('rhodecode.api.utils._get_ref_hash')\ |
|
111 | 111 | as _get_ref_hash: |
|
112 | 112 | _get_ref_hash.side_effect = KeyError() |
|
113 | 113 | with pytest.raises(JSONRPCError) as excinfo: |
|
114 | 114 | utils.resolve_ref_or_error(ref, repo) |
|
115 | 115 | expected_message = ( |
|
116 | 116 | 'The specified value:bookmark:`non-existing-one` does not exist, or is not allowed.') |
|
117 | 117 | assert excinfo.value.message == expected_message |
|
118 | 118 | |
|
119 | 119 | @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d']) |
|
120 | 120 | def test_ref_cannot_be_parsed(self, ref): |
|
121 | 121 | repo = Mock() |
|
122 | 122 | with pytest.raises(JSONRPCError) as excinfo: |
|
123 | 123 | utils.resolve_ref_or_error(ref, repo) |
|
124 | 124 | expected_message = ( |
|
125 | 125 | 'Ref `{ref}` given in a wrong format. Please check the API' |
|
126 | 126 | ' documentation for more details'.format(ref=ref) |
|
127 | 127 | ) |
|
128 | 128 | assert excinfo.value.message == expected_message |
|
129 | 129 | |
|
130 | 130 | |
|
131 | 131 | class TestGetRefHash(object): |
|
132 | 132 | |
|
133 | 133 | def setup_method(self): |
|
134 | 134 | self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10' |
|
135 | 135 | self.bookmark_name = 'test-bookmark' |
|
136 | 136 | |
|
137 | 137 | @pytest.mark.parametrize("alias, branch_name", [ |
|
138 | 138 | ("git", "master"), |
|
139 | 139 | ("hg", "default") |
|
140 | 140 | ]) |
|
141 | 141 | def test_returns_hash_by_branch_name(self, alias, branch_name): |
|
142 | 142 | with patch('rhodecode.model.db.Repository') as repo: |
|
143 | 143 | repo.scm_instance().alias = alias |
|
144 | 144 | repo.scm_instance().branches = {branch_name: self.commit_hash} |
|
145 | 145 | result_hash = utils._get_ref_hash(repo, 'branch', branch_name) |
|
146 | 146 | assert result_hash == self.commit_hash |
|
147 | 147 | |
|
148 | 148 | @pytest.mark.parametrize("alias, branch_name", [ |
|
149 | 149 | ("git", "master"), |
|
150 | 150 | ("hg", "default") |
|
151 | 151 | ]) |
|
152 | 152 | def test_raises_error_when_branch_is_not_found(self, alias, branch_name): |
|
153 | 153 | with patch('rhodecode.model.db.Repository') as repo: |
|
154 | 154 | repo.scm_instance().alias = alias |
|
155 | 155 | repo.scm_instance().branches = {} |
|
156 | 156 | with pytest.raises(KeyError): |
|
157 | 157 | utils._get_ref_hash(repo, 'branch', branch_name) |
|
158 | 158 | |
|
159 | 159 | def test_returns_hash_when_bookmark_is_specified_for_hg(self): |
|
160 | 160 | with patch('rhodecode.model.db.Repository') as repo: |
|
161 | 161 | repo.scm_instance().alias = 'hg' |
|
162 | 162 | repo.scm_instance().bookmarks = { |
|
163 | 163 | self.bookmark_name: self.commit_hash} |
|
164 | 164 | result_hash = utils._get_ref_hash( |
|
165 | 165 | repo, 'bookmark', self.bookmark_name) |
|
166 | 166 | assert result_hash == self.commit_hash |
|
167 | 167 | |
|
168 | 168 | def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self): |
|
169 | 169 | with patch('rhodecode.model.db.Repository') as repo: |
|
170 | 170 | repo.scm_instance().alias = 'hg' |
|
171 | 171 | repo.scm_instance().bookmarks = {} |
|
172 | 172 | with pytest.raises(KeyError): |
|
173 | 173 | utils._get_ref_hash(repo, 'bookmark', self.bookmark_name) |
|
174 | 174 | |
|
175 | 175 | def test_raises_error_when_bookmark_is_specified_for_git(self): |
|
176 | 176 | with patch('rhodecode.model.db.Repository') as repo: |
|
177 | 177 | repo.scm_instance().alias = 'git' |
|
178 | 178 | repo.scm_instance().bookmarks = { |
|
179 | 179 | self.bookmark_name: self.commit_hash} |
|
180 | 180 | with pytest.raises(ValueError): |
|
181 | 181 | utils._get_ref_hash(repo, 'bookmark', self.bookmark_name) |
|
182 | 182 | |
|
183 | 183 | |
|
184 | 184 | class TestUserByNameOrError(object): |
|
185 | 185 | def test_user_found_by_id(self): |
|
186 | 186 | fake_user = Mock(id=123) |
|
187 | 187 | |
|
188 | 188 | patcher = patch('rhodecode.model.user.UserModel.get_user') |
|
189 | 189 | with patcher as get_user: |
|
190 | 190 | get_user.return_value = fake_user |
|
191 | 191 | |
|
192 | 192 | patcher = patch('rhodecode.model.user.UserModel.get_by_username') |
|
193 | 193 | with patcher as get_by_username: |
|
194 | 194 | result = utils.get_user_or_error(123) |
|
195 | 195 | assert result == fake_user |
|
196 | 196 | |
|
197 | 197 | def test_user_not_found_by_id_as_str(self): |
|
198 | 198 | fake_user = Mock(id=123) |
|
199 | 199 | |
|
200 | 200 | patcher = patch('rhodecode.model.user.UserModel.get_user') |
|
201 | 201 | with patcher as get_user: |
|
202 | 202 | get_user.return_value = fake_user |
|
203 | 203 | patcher = patch('rhodecode.model.user.UserModel.get_by_username') |
|
204 | 204 | with patcher as get_by_username: |
|
205 | 205 | get_by_username.return_value = None |
|
206 | 206 | |
|
207 | 207 | with pytest.raises(JSONRPCError): |
|
208 | 208 | utils.get_user_or_error('123') |
|
209 | 209 | |
|
210 | 210 | def test_user_found_by_name(self): |
|
211 | 211 | fake_user = Mock(id=123) |
|
212 | 212 | |
|
213 | 213 | patcher = patch('rhodecode.model.user.UserModel.get_user') |
|
214 | 214 | with patcher as get_user: |
|
215 | 215 | get_user.return_value = None |
|
216 | 216 | |
|
217 | 217 | patcher = patch('rhodecode.model.user.UserModel.get_by_username') |
|
218 | 218 | with patcher as get_by_username: |
|
219 | 219 | get_by_username.return_value = fake_user |
|
220 | 220 | |
|
221 | 221 | result = utils.get_user_or_error('test') |
|
222 | 222 | assert result == fake_user |
|
223 | 223 | |
|
224 | 224 | def test_user_not_found_by_id(self): |
|
225 | 225 | patcher = patch('rhodecode.model.user.UserModel.get_user') |
|
226 | 226 | with patcher as get_user: |
|
227 | 227 | get_user.return_value = None |
|
228 | 228 | patcher = patch('rhodecode.model.user.UserModel.get_by_username') |
|
229 | 229 | with patcher as get_by_username: |
|
230 | 230 | get_by_username.return_value = None |
|
231 | 231 | |
|
232 | 232 | with pytest.raises(JSONRPCError) as excinfo: |
|
233 | 233 | utils.get_user_or_error(123) |
|
234 | 234 | |
|
235 | 235 | expected_message = 'user `123` does not exist' |
|
236 | 236 | assert excinfo.value.message == expected_message |
|
237 | 237 | |
|
238 | 238 | def test_user_not_found_by_name(self): |
|
239 | 239 | patcher = patch('rhodecode.model.user.UserModel.get_by_username') |
|
240 | 240 | with patcher as get_by_username: |
|
241 | 241 | get_by_username.return_value = None |
|
242 | 242 | with pytest.raises(JSONRPCError) as excinfo: |
|
243 | 243 | utils.get_user_or_error('test') |
|
244 | 244 | |
|
245 | 245 | expected_message = 'user `test` does not exist' |
|
246 | 246 | assert excinfo.value.message == expected_message |
|
247 | 247 | |
|
248 | 248 | |
|
249 | 249 | class TestGetCommitDict(object): |
|
250 | 250 | @pytest.mark.parametrize('filename, expected', [ |
|
251 | 251 | (b'sp\xc3\xa4cial', u'sp\xe4cial'), |
|
252 | 252 | (b'sp\xa4cial', u'sp\ufffdcial'), |
|
253 | 253 | ]) |
|
254 | 254 | def test_decodes_filenames_to_unicode(self, filename, expected): |
|
255 | 255 | result = utils._get_commit_dict(filename=filename, op='A') |
|
256 | 256 | assert result['filename'] == expected |
|
257 | 257 | |
|
258 | 258 | |
|
259 | 259 | class TestRepoAccess(object): |
|
260 | 260 | def setup_method(self, method): |
|
261 | 261 | |
|
262 | 262 | self.admin_perm_patch = patch( |
|
263 | 263 | 'rhodecode.api.utils.HasPermissionAnyApi') |
|
264 | 264 | self.repo_perm_patch = patch( |
|
265 | 265 | 'rhodecode.api.utils.HasRepoPermissionAnyApi') |
|
266 | 266 | |
|
267 | 267 | def test_has_superadmin_permission_checks_for_admin(self): |
|
268 | 268 | admin_mock = Mock() |
|
269 | 269 | with self.admin_perm_patch as amock: |
|
270 | 270 | amock.return_value = admin_mock |
|
271 | 271 | assert utils.has_superadmin_permission('fake_user') |
|
272 | 272 | amock.assert_called_once_with('hg.admin') |
|
273 | 273 | |
|
274 | 274 | admin_mock.assert_called_once_with(user='fake_user') |
|
275 | 275 | |
|
276 | 276 | def test_has_repo_permissions_checks_for_repo_access(self): |
|
277 | 277 | repo_mock = Mock() |
|
278 | 278 | fake_repo = Mock() |
|
279 | 279 | with self.repo_perm_patch as rmock: |
|
280 | 280 | rmock.return_value = repo_mock |
|
281 | 281 | assert utils.validate_repo_permissions( |
|
282 | 282 | 'fake_user', 'fake_repo_id', fake_repo, |
|
283 | 283 | ['perm1', 'perm2']) |
|
284 | 284 | rmock.assert_called_once_with(*['perm1', 'perm2']) |
|
285 | 285 | |
|
286 | 286 | repo_mock.assert_called_once_with( |
|
287 | 287 | user='fake_user', repo_name=fake_repo.repo_name) |
|
288 | 288 | |
|
289 | 289 | def test_has_repo_permissions_raises_not_found(self): |
|
290 | 290 | repo_mock = Mock(return_value=False) |
|
291 | 291 | fake_repo = Mock() |
|
292 | 292 | with self.repo_perm_patch as rmock: |
|
293 | 293 | rmock.return_value = repo_mock |
|
294 | 294 | with pytest.raises(JSONRPCError) as excinfo: |
|
295 | 295 | utils.validate_repo_permissions( |
|
296 | 296 | 'fake_user', 'fake_repo_id', fake_repo, 'perms') |
|
297 | 297 | assert 'fake_repo_id' in excinfo |
@@ -1,123 +1,123 b'' | |||
|
1 | 1 | |
|
2 |
# Copyright (C) 2010-202 |
|
|
2 | # Copyright (C) 2010-2023 RhodeCode GmbH | |
|
3 | 3 | # |
|
4 | 4 | # This program is free software: you can redistribute it and/or modify |
|
5 | 5 | # it under the terms of the GNU Affero General Public License, version 3 |
|
6 | 6 | # (only), as published by the Free Software Foundation. |
|
7 | 7 | # |
|
8 | 8 | # This program is distributed in the hope that it will be useful, |
|
9 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11 | 11 | # GNU General Public License for more details. |
|
12 | 12 | # |
|
13 | 13 | # You should have received a copy of the GNU Affero General Public License |
|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
15 | 15 | # |
|
16 | 16 | # This program is dual-licensed. If you wish to learn more about the |
|
17 | 17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
18 | 18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
19 | 19 | |
|
20 | 20 | |
|
21 | 21 | import random |
|
22 | 22 | import pytest |
|
23 | 23 | |
|
24 | 24 | from rhodecode.api.utils import get_origin |
|
25 | 25 | from rhodecode.lib.ext_json import json |
|
26 | 26 | |
|
27 | 27 | |
|
28 | 28 | def jsonify(obj): |
|
29 | 29 | return json.loads(json.dumps(obj)) |
|
30 | 30 | |
|
31 | 31 | |
|
32 | 32 | API_URL = '/_admin/api' |
|
33 | 33 | |
|
34 | 34 | |
|
35 | 35 | def assert_call_ok(id_, given): |
|
36 | 36 | expected = jsonify({ |
|
37 | 37 | 'id': id_, |
|
38 | 38 | 'error': None, |
|
39 | 39 | 'result': None |
|
40 | 40 | }) |
|
41 | 41 | given = json.loads(given) |
|
42 | 42 | |
|
43 | 43 | assert expected['id'] == given['id'] |
|
44 | 44 | assert expected['error'] == given['error'] |
|
45 | 45 | return given['result'] |
|
46 | 46 | |
|
47 | 47 | |
|
48 | 48 | def assert_ok(id_, expected, given): |
|
49 | 49 | given = json.loads(given) |
|
50 | 50 | if given.get('error'): |
|
51 | 51 | err = given['error'] |
|
52 | 52 | pytest.fail(f"Unexpected ERROR in expected success response: `{err}`") |
|
53 | 53 | |
|
54 | 54 | expected = jsonify({ |
|
55 | 55 | 'id': id_, |
|
56 | 56 | 'error': None, |
|
57 | 57 | 'result': expected |
|
58 | 58 | }) |
|
59 | 59 | |
|
60 | 60 | assert expected == given |
|
61 | 61 | |
|
62 | 62 | |
|
63 | 63 | def assert_error(id_, expected, given): |
|
64 | 64 | expected = jsonify({ |
|
65 | 65 | 'id': id_, |
|
66 | 66 | 'error': expected, |
|
67 | 67 | 'result': None |
|
68 | 68 | }) |
|
69 | 69 | given = json.loads(given) |
|
70 | 70 | assert expected == given |
|
71 | 71 | |
|
72 | 72 | |
|
73 | 73 | def build_data(apikey, method, **kw): |
|
74 | 74 | """ |
|
75 | 75 | Builds API data with given random ID |
|
76 | 76 | """ |
|
77 | 77 | random_id = random.randrange(1, 9999) |
|
78 | 78 | return random_id, json.dumps({ |
|
79 | 79 | "id": random_id, |
|
80 | 80 | "api_key": apikey, |
|
81 | 81 | "method": method, |
|
82 | 82 | "args": kw |
|
83 | 83 | }) |
|
84 | 84 | |
|
85 | 85 | |
|
86 | 86 | def api_call(app, params, status=None): |
|
87 | 87 | response = app.post( |
|
88 | 88 | API_URL, content_type='application/json', params=params, status=status, |
|
89 | 89 | headers=[('Content-Type', 'application/json')]) |
|
90 | 90 | return response |
|
91 | 91 | |
|
92 | 92 | |
|
93 | 93 | def crash(*args, **kwargs): |
|
94 | 94 | raise Exception('Total Crash !') |
|
95 | 95 | |
|
96 | 96 | |
|
97 | 97 | def expected_permissions(object_with_permissions): |
|
98 | 98 | """ |
|
99 | 99 | Returns the expected permissions structure for the given object. |
|
100 | 100 | |
|
101 | 101 | The object is expected to be a `Repository`, `RepositoryGroup`, |
|
102 | 102 | or `UserGroup`. They all implement the same permission handling |
|
103 | 103 | API. |
|
104 | 104 | """ |
|
105 | 105 | permissions = [] |
|
106 | 106 | for _user in object_with_permissions.permissions(): |
|
107 | 107 | user_data = { |
|
108 | 108 | 'name': _user.username, |
|
109 | 109 | 'permission': _user.permission, |
|
110 | 110 | 'origin': get_origin(_user), |
|
111 | 111 | 'type': "user", |
|
112 | 112 | } |
|
113 | 113 | permissions.append(user_data) |
|
114 | 114 | |
|
115 | 115 | for _user_group in object_with_permissions.permission_user_groups(): |
|
116 | 116 | user_group_data = { |
|
117 | 117 | 'name': _user_group.users_group_name, |
|
118 | 118 | 'permission': _user_group.permission, |
|
119 | 119 | 'origin': get_origin(_user_group), |
|
120 | 120 | 'type': "user_group", |
|
121 | 121 | } |
|
122 | 122 | permissions.append(user_group_data) |
|
123 | 123 | return permissions |
@@ -1,458 +1,458 b'' | |||
|
1 | 1 | |
|
2 | 2 | |
|
3 |
# Copyright (C) 2014-202 |
|
|
3 | # Copyright (C) 2014-2023 RhodeCode GmbH | |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | """ |
|
22 | 22 | JSON RPC utils |
|
23 | 23 | """ |
|
24 | 24 | |
|
25 | 25 | import collections |
|
26 | 26 | import logging |
|
27 | 27 | |
|
28 | 28 | from rhodecode.api.exc import JSONRPCError |
|
29 | 29 | from rhodecode.lib.auth import ( |
|
30 | 30 | HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi) |
|
31 | 31 | from rhodecode.lib.str_utils import safe_str |
|
32 | 32 | from rhodecode.lib.vcs.exceptions import RepositoryError |
|
33 | 33 | from rhodecode.lib.view_utils import get_commit_from_ref_name |
|
34 | 34 | from rhodecode.lib.utils2 import str2bool |
|
35 | 35 | |
|
36 | 36 | log = logging.getLogger(__name__) |
|
37 | 37 | |
|
38 | 38 | |
|
39 | 39 | class OAttr(object): |
|
40 | 40 | """ |
|
41 | 41 | Special Option that defines other attribute, and can default to them |
|
42 | 42 | |
|
43 | 43 | Example:: |
|
44 | 44 | |
|
45 | 45 | def test(apiuser, userid=Optional(OAttr('apiuser')): |
|
46 | 46 | user = Optional.extract(userid, evaluate_locals=local()) |
|
47 | 47 | #if we pass in userid, we get it, else it will default to apiuser |
|
48 | 48 | #attribute |
|
49 | 49 | """ |
|
50 | 50 | |
|
51 | 51 | def __init__(self, attr_name): |
|
52 | 52 | self.attr_name = attr_name |
|
53 | 53 | |
|
54 | 54 | def __repr__(self): |
|
55 | 55 | return '<OptionalAttr:%s>' % self.attr_name |
|
56 | 56 | |
|
57 | 57 | def __call__(self): |
|
58 | 58 | return self |
|
59 | 59 | |
|
60 | 60 | |
|
61 | 61 | class Optional(object): |
|
62 | 62 | """ |
|
63 | 63 | Defines an optional parameter:: |
|
64 | 64 | |
|
65 | 65 | param = param.getval() if isinstance(param, Optional) else param |
|
66 | 66 | param = param() if isinstance(param, Optional) else param |
|
67 | 67 | |
|
68 | 68 | is equivalent of:: |
|
69 | 69 | |
|
70 | 70 | param = Optional.extract(param) |
|
71 | 71 | |
|
72 | 72 | """ |
|
73 | 73 | |
|
74 | 74 | def __init__(self, type_): |
|
75 | 75 | self.type_ = type_ |
|
76 | 76 | |
|
77 | 77 | def __repr__(self): |
|
78 | 78 | return '<Optional:%s>' % self.type_.__repr__() |
|
79 | 79 | |
|
80 | 80 | def __call__(self): |
|
81 | 81 | return self.getval() |
|
82 | 82 | |
|
83 | 83 | def getval(self, evaluate_locals=None): |
|
84 | 84 | """ |
|
85 | 85 | returns value from this Optional instance |
|
86 | 86 | """ |
|
87 | 87 | if isinstance(self.type_, OAttr): |
|
88 | 88 | param_name = self.type_.attr_name |
|
89 | 89 | if evaluate_locals: |
|
90 | 90 | return evaluate_locals[param_name] |
|
91 | 91 | # use params name |
|
92 | 92 | return param_name |
|
93 | 93 | return self.type_ |
|
94 | 94 | |
|
95 | 95 | @classmethod |
|
96 | 96 | def extract(cls, val, evaluate_locals=None, binary=None): |
|
97 | 97 | """ |
|
98 | 98 | Extracts value from Optional() instance |
|
99 | 99 | |
|
100 | 100 | :param val: |
|
101 | 101 | :return: original value if it's not Optional instance else |
|
102 | 102 | value of instance |
|
103 | 103 | """ |
|
104 | 104 | if isinstance(val, cls): |
|
105 | 105 | val = val.getval(evaluate_locals) |
|
106 | 106 | |
|
107 | 107 | if binary: |
|
108 | 108 | val = str2bool(val) |
|
109 | 109 | |
|
110 | 110 | return val |
|
111 | 111 | |
|
112 | 112 | |
|
113 | 113 | def parse_args(cli_args, key_prefix=''): |
|
114 | 114 | from rhodecode.lib.utils2 import (escape_split) |
|
115 | 115 | kwargs = collections.defaultdict(dict) |
|
116 | 116 | for el in escape_split(cli_args, ','): |
|
117 | 117 | kv = escape_split(el, '=', 1) |
|
118 | 118 | if len(kv) == 2: |
|
119 | 119 | k, v = kv |
|
120 | 120 | kwargs[key_prefix + k] = v |
|
121 | 121 | return kwargs |
|
122 | 122 | |
|
123 | 123 | |
|
124 | 124 | def get_origin(obj): |
|
125 | 125 | """ |
|
126 | 126 | Get origin of permission from object. |
|
127 | 127 | |
|
128 | 128 | :param obj: |
|
129 | 129 | """ |
|
130 | 130 | origin = 'permission' |
|
131 | 131 | |
|
132 | 132 | if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''): |
|
133 | 133 | # admin and owner case, maybe we should use dual string ? |
|
134 | 134 | origin = 'owner' |
|
135 | 135 | elif getattr(obj, 'owner_row', ''): |
|
136 | 136 | origin = 'owner' |
|
137 | 137 | elif getattr(obj, 'admin_row', ''): |
|
138 | 138 | origin = 'super-admin' |
|
139 | 139 | return origin |
|
140 | 140 | |
|
141 | 141 | |
|
142 | 142 | def store_update(updates, attr, name): |
|
143 | 143 | """ |
|
144 | 144 | Stores param in updates dict if it's not instance of Optional |
|
145 | 145 | allows easy updates of passed in params |
|
146 | 146 | """ |
|
147 | 147 | if not isinstance(attr, Optional): |
|
148 | 148 | updates[name] = attr |
|
149 | 149 | |
|
150 | 150 | |
|
151 | 151 | def has_superadmin_permission(apiuser): |
|
152 | 152 | """ |
|
153 | 153 | Return True if apiuser is admin or return False |
|
154 | 154 | |
|
155 | 155 | :param apiuser: |
|
156 | 156 | """ |
|
157 | 157 | if HasPermissionAnyApi('hg.admin')(user=apiuser): |
|
158 | 158 | return True |
|
159 | 159 | return False |
|
160 | 160 | |
|
161 | 161 | |
|
162 | 162 | def validate_repo_permissions(apiuser, repoid, repo, perms): |
|
163 | 163 | """ |
|
164 | 164 | Raise JsonRPCError if apiuser is not authorized or return True |
|
165 | 165 | |
|
166 | 166 | :param apiuser: |
|
167 | 167 | :param repoid: |
|
168 | 168 | :param repo: |
|
169 | 169 | :param perms: |
|
170 | 170 | """ |
|
171 | 171 | if not HasRepoPermissionAnyApi(*perms)( |
|
172 | 172 | user=apiuser, repo_name=repo.repo_name): |
|
173 | 173 | raise JSONRPCError('repository `%s` does not exist' % repoid) |
|
174 | 174 | |
|
175 | 175 | return True |
|
176 | 176 | |
|
177 | 177 | |
|
178 | 178 | def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms): |
|
179 | 179 | """ |
|
180 | 180 | Raise JsonRPCError if apiuser is not authorized or return True |
|
181 | 181 | |
|
182 | 182 | :param apiuser: |
|
183 | 183 | :param repogroupid: just the id of repository group |
|
184 | 184 | :param repo_group: instance of repo_group |
|
185 | 185 | :param perms: |
|
186 | 186 | """ |
|
187 | 187 | if not HasRepoGroupPermissionAnyApi(*perms)( |
|
188 | 188 | user=apiuser, group_name=repo_group.group_name): |
|
189 | 189 | raise JSONRPCError( |
|
190 | 190 | 'repository group `%s` does not exist' % repogroupid) |
|
191 | 191 | |
|
192 | 192 | return True |
|
193 | 193 | |
|
194 | 194 | |
|
195 | 195 | def validate_set_owner_permissions(apiuser, owner): |
|
196 | 196 | if isinstance(owner, Optional): |
|
197 | 197 | owner = get_user_or_error(apiuser.user_id) |
|
198 | 198 | else: |
|
199 | 199 | if has_superadmin_permission(apiuser): |
|
200 | 200 | owner = get_user_or_error(owner) |
|
201 | 201 | else: |
|
202 | 202 | # forbid setting owner for non-admins |
|
203 | 203 | raise JSONRPCError( |
|
204 | 204 | 'Only RhodeCode super-admin can specify `owner` param') |
|
205 | 205 | return owner |
|
206 | 206 | |
|
207 | 207 | |
|
208 | 208 | def get_user_or_error(userid): |
|
209 | 209 | """ |
|
210 | 210 | Get user by id or name or return JsonRPCError if not found |
|
211 | 211 | |
|
212 | 212 | :param userid: |
|
213 | 213 | """ |
|
214 | 214 | from rhodecode.model.user import UserModel |
|
215 | 215 | user_model = UserModel() |
|
216 | 216 | |
|
217 | 217 | if isinstance(userid, int): |
|
218 | 218 | try: |
|
219 | 219 | user = user_model.get_user(userid) |
|
220 | 220 | except ValueError: |
|
221 | 221 | user = None |
|
222 | 222 | else: |
|
223 | 223 | user = user_model.get_by_username(userid) |
|
224 | 224 | |
|
225 | 225 | if user is None: |
|
226 | 226 | raise JSONRPCError( |
|
227 | 227 | 'user `%s` does not exist' % (userid,)) |
|
228 | 228 | return user |
|
229 | 229 | |
|
230 | 230 | |
|
231 | 231 | def get_repo_or_error(repoid): |
|
232 | 232 | """ |
|
233 | 233 | Get repo by id or name or return JsonRPCError if not found |
|
234 | 234 | |
|
235 | 235 | :param repoid: |
|
236 | 236 | """ |
|
237 | 237 | from rhodecode.model.repo import RepoModel |
|
238 | 238 | repo_model = RepoModel() |
|
239 | 239 | |
|
240 | 240 | if isinstance(repoid, int): |
|
241 | 241 | try: |
|
242 | 242 | repo = repo_model.get_repo(repoid) |
|
243 | 243 | except ValueError: |
|
244 | 244 | repo = None |
|
245 | 245 | else: |
|
246 | 246 | repo = repo_model.get_by_repo_name(repoid) |
|
247 | 247 | |
|
248 | 248 | if repo is None: |
|
249 | 249 | raise JSONRPCError( |
|
250 | 250 | 'repository `%s` does not exist' % (repoid,)) |
|
251 | 251 | return repo |
|
252 | 252 | |
|
253 | 253 | |
|
254 | 254 | def get_repo_group_or_error(repogroupid): |
|
255 | 255 | """ |
|
256 | 256 | Get repo group by id or name or return JsonRPCError if not found |
|
257 | 257 | |
|
258 | 258 | :param repogroupid: |
|
259 | 259 | """ |
|
260 | 260 | from rhodecode.model.repo_group import RepoGroupModel |
|
261 | 261 | repo_group_model = RepoGroupModel() |
|
262 | 262 | |
|
263 | 263 | if isinstance(repogroupid, int): |
|
264 | 264 | try: |
|
265 | 265 | repo_group = repo_group_model._get_repo_group(repogroupid) |
|
266 | 266 | except ValueError: |
|
267 | 267 | repo_group = None |
|
268 | 268 | else: |
|
269 | 269 | repo_group = repo_group_model.get_by_group_name(repogroupid) |
|
270 | 270 | |
|
271 | 271 | if repo_group is None: |
|
272 | 272 | raise JSONRPCError( |
|
273 | 273 | 'repository group `%s` does not exist' % (repogroupid,)) |
|
274 | 274 | return repo_group |
|
275 | 275 | |
|
276 | 276 | |
|
277 | 277 | def get_user_group_or_error(usergroupid): |
|
278 | 278 | """ |
|
279 | 279 | Get user group by id or name or return JsonRPCError if not found |
|
280 | 280 | |
|
281 | 281 | :param usergroupid: |
|
282 | 282 | """ |
|
283 | 283 | from rhodecode.model.user_group import UserGroupModel |
|
284 | 284 | user_group_model = UserGroupModel() |
|
285 | 285 | |
|
286 | 286 | if isinstance(usergroupid, int): |
|
287 | 287 | try: |
|
288 | 288 | user_group = user_group_model.get_group(usergroupid) |
|
289 | 289 | except ValueError: |
|
290 | 290 | user_group = None |
|
291 | 291 | else: |
|
292 | 292 | user_group = user_group_model.get_by_name(usergroupid) |
|
293 | 293 | |
|
294 | 294 | if user_group is None: |
|
295 | 295 | raise JSONRPCError( |
|
296 | 296 | 'user group `%s` does not exist' % (usergroupid,)) |
|
297 | 297 | return user_group |
|
298 | 298 | |
|
299 | 299 | |
|
300 | 300 | def get_perm_or_error(permid, prefix=None): |
|
301 | 301 | """ |
|
302 | 302 | Get permission by id or name or return JsonRPCError if not found |
|
303 | 303 | |
|
304 | 304 | :param permid: |
|
305 | 305 | """ |
|
306 | 306 | from rhodecode.model.permission import PermissionModel |
|
307 | 307 | |
|
308 | 308 | perm = PermissionModel.cls.get_by_key(permid) |
|
309 | 309 | if perm is None: |
|
310 | 310 | msg = 'permission `{}` does not exist.'.format(permid) |
|
311 | 311 | if prefix: |
|
312 | 312 | msg += ' Permission should start with prefix: `{}`'.format(prefix) |
|
313 | 313 | raise JSONRPCError(msg) |
|
314 | 314 | |
|
315 | 315 | if prefix: |
|
316 | 316 | if not perm.permission_name.startswith(prefix): |
|
317 | 317 | raise JSONRPCError('permission `%s` is invalid, ' |
|
318 | 318 | 'should start with %s' % (permid, prefix)) |
|
319 | 319 | return perm |
|
320 | 320 | |
|
321 | 321 | |
|
322 | 322 | def get_gist_or_error(gistid): |
|
323 | 323 | """ |
|
324 | 324 | Get gist by id or gist_access_id or return JsonRPCError if not found |
|
325 | 325 | |
|
326 | 326 | :param gistid: |
|
327 | 327 | """ |
|
328 | 328 | from rhodecode.model.gist import GistModel |
|
329 | 329 | |
|
330 | 330 | gist = GistModel.cls.get_by_access_id(gistid) |
|
331 | 331 | if gist is None: |
|
332 | 332 | raise JSONRPCError('gist `%s` does not exist' % (gistid,)) |
|
333 | 333 | return gist |
|
334 | 334 | |
|
335 | 335 | |
|
336 | 336 | def get_pull_request_or_error(pullrequestid): |
|
337 | 337 | """ |
|
338 | 338 | Get pull request by id or return JsonRPCError if not found |
|
339 | 339 | |
|
340 | 340 | :param pullrequestid: |
|
341 | 341 | """ |
|
342 | 342 | from rhodecode.model.pull_request import PullRequestModel |
|
343 | 343 | |
|
344 | 344 | try: |
|
345 | 345 | pull_request = PullRequestModel().get(int(pullrequestid)) |
|
346 | 346 | except ValueError: |
|
347 | 347 | raise JSONRPCError('pullrequestid must be an integer') |
|
348 | 348 | if not pull_request: |
|
349 | 349 | raise JSONRPCError('pull request `%s` does not exist' % ( |
|
350 | 350 | pullrequestid,)) |
|
351 | 351 | return pull_request |
|
352 | 352 | |
|
353 | 353 | |
|
354 | 354 | def build_commit_data(rhodecode_vcs_repo, commit, detail_level): |
|
355 | 355 | commit2 = commit |
|
356 | 356 | commit1 = commit.first_parent |
|
357 | 357 | |
|
358 | 358 | parsed_diff = [] |
|
359 | 359 | if detail_level == 'extended': |
|
360 | 360 | for f_path in commit.added_paths: |
|
361 | 361 | parsed_diff.append(_get_commit_dict(filename=f_path, op='A')) |
|
362 | 362 | for f_path in commit.changed_paths: |
|
363 | 363 | parsed_diff.append(_get_commit_dict(filename=f_path, op='M')) |
|
364 | 364 | for f_path in commit.removed_paths: |
|
365 | 365 | parsed_diff.append(_get_commit_dict(filename=f_path, op='D')) |
|
366 | 366 | |
|
367 | 367 | elif detail_level == 'full': |
|
368 | 368 | from rhodecode.lib import diffs |
|
369 | 369 | |
|
370 | 370 | _diff = rhodecode_vcs_repo.get_diff(commit1, commit2,) |
|
371 | 371 | diff_processor = diffs.DiffProcessor(_diff, diff_format='newdiff', show_full_diff=True) |
|
372 | 372 | |
|
373 | 373 | for dp in diff_processor.prepare(): |
|
374 | 374 | del dp['stats']['ops'] |
|
375 | 375 | _stats = dp['stats'] |
|
376 | 376 | parsed_diff.append(_get_commit_dict( |
|
377 | 377 | filename=dp['filename'], op=dp['operation'], |
|
378 | 378 | new_revision=dp['new_revision'], |
|
379 | 379 | old_revision=dp['old_revision'], |
|
380 | 380 | raw_diff=dp['raw_diff'], stats=_stats)) |
|
381 | 381 | |
|
382 | 382 | return parsed_diff |
|
383 | 383 | |
|
384 | 384 | |
|
385 | 385 | def get_commit_or_error(ref, repo): |
|
386 | 386 | try: |
|
387 | 387 | ref_type, _, ref_hash = ref.split(':') |
|
388 | 388 | except ValueError: |
|
389 | 389 | raise JSONRPCError( |
|
390 | 390 | 'Ref `{ref}` given in a wrong format. Please check the API' |
|
391 | 391 | ' documentation for more details'.format(ref=ref)) |
|
392 | 392 | try: |
|
393 | 393 | # TODO: dan: refactor this to use repo.scm_instance().get_commit() |
|
394 | 394 | # once get_commit supports ref_types |
|
395 | 395 | return get_commit_from_ref_name(repo, ref_hash) |
|
396 | 396 | except RepositoryError: |
|
397 | 397 | raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref)) |
|
398 | 398 | |
|
399 | 399 | |
|
400 | 400 | def _get_ref_hash(repo, type_, name): |
|
401 | 401 | vcs_repo = repo.scm_instance() |
|
402 | 402 | if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'): |
|
403 | 403 | return vcs_repo.branches[name] |
|
404 | 404 | elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg': |
|
405 | 405 | return vcs_repo.bookmarks[name] |
|
406 | 406 | else: |
|
407 | 407 | raise ValueError() |
|
408 | 408 | |
|
409 | 409 | |
|
410 | 410 | def resolve_ref_or_error(ref, repo, allowed_ref_types=None): |
|
411 | 411 | allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch'] |
|
412 | 412 | |
|
413 | 413 | def _parse_ref(type_, name, hash_=None): |
|
414 | 414 | return type_, name, hash_ |
|
415 | 415 | |
|
416 | 416 | try: |
|
417 | 417 | ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':')) |
|
418 | 418 | except TypeError: |
|
419 | 419 | raise JSONRPCError( |
|
420 | 420 | 'Ref `{ref}` given in a wrong format. Please check the API' |
|
421 | 421 | ' documentation for more details'.format(ref=ref)) |
|
422 | 422 | |
|
423 | 423 | if ref_type not in allowed_ref_types: |
|
424 | 424 | raise JSONRPCError( |
|
425 | 425 | 'Ref `{ref}` type is not allowed. ' |
|
426 | 426 | 'Only:{allowed_refs} are possible.'.format( |
|
427 | 427 | ref=ref, allowed_refs=allowed_ref_types)) |
|
428 | 428 | |
|
429 | 429 | try: |
|
430 | 430 | ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name) |
|
431 | 431 | except (KeyError, ValueError): |
|
432 | 432 | raise JSONRPCError( |
|
433 | 433 | 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format( |
|
434 | 434 | type=ref_type, name=ref_name)) |
|
435 | 435 | |
|
436 | 436 | return ':'.join([ref_type, ref_name, ref_hash]) |
|
437 | 437 | |
|
438 | 438 | |
|
439 | 439 | def _get_commit_dict( |
|
440 | 440 | filename, op, new_revision=None, old_revision=None, |
|
441 | 441 | raw_diff=None, stats=None): |
|
442 | 442 | if stats is None: |
|
443 | 443 | stats = { |
|
444 | 444 | "added": None, |
|
445 | 445 | "binary": None, |
|
446 | 446 | "deleted": None |
|
447 | 447 | } |
|
448 | 448 | return { |
|
449 | 449 | "filename": safe_str(filename), |
|
450 | 450 | "op": op, |
|
451 | 451 | |
|
452 | 452 | # extra details |
|
453 | 453 | "new_revision": new_revision, |
|
454 | 454 | "old_revision": old_revision, |
|
455 | 455 | |
|
456 | 456 | "raw_diff": raw_diff, |
|
457 | 457 | "stats": stats |
|
458 | 458 | } |
@@ -1,19 +1,19 b'' | |||
|
1 | 1 | |
|
2 | 2 | |
|
3 |
# Copyright (C) 2015-202 |
|
|
3 | # Copyright (C) 2015-2023 RhodeCode GmbH | |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
@@ -1,102 +1,102 b'' | |||
|
1 | 1 | |
|
2 | 2 | |
|
3 |
# Copyright (C) 2011-202 |
|
|
3 | # Copyright (C) 2011-2023 RhodeCode GmbH | |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | """ |
|
23 | 23 | NOTE: |
|
24 | 24 | Place for deprecated APIs here, if a call needs to be deprecated, please |
|
25 | 25 | put it here, and point to a new version |
|
26 | 26 | """ |
|
27 | 27 | import logging |
|
28 | 28 | |
|
29 | 29 | from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method |
|
30 | 30 | from rhodecode.api.utils import Optional, OAttr |
|
31 | 31 | |
|
32 | 32 | |
|
33 | 33 | log = logging.getLogger(__name__) |
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | # permission check inside |
|
37 | 37 | @jsonrpc_method() |
|
38 | 38 | @jsonrpc_deprecated_method( |
|
39 | 39 | use_method='comment_commit', deprecated_at_version='3.4.0') |
|
40 | 40 | def changeset_comment(request, apiuser, repoid, revision, message, |
|
41 | 41 | userid=Optional(OAttr('apiuser')), |
|
42 | 42 | status=Optional(None)): |
|
43 | 43 | """ |
|
44 | 44 | Set a changeset comment, and optionally change the status of the |
|
45 | 45 | changeset. |
|
46 | 46 | |
|
47 | 47 | This command can only be run using an |authtoken| with admin |
|
48 | 48 | permissions on the |repo|. |
|
49 | 49 | |
|
50 | 50 | :param apiuser: This is filled automatically from the |authtoken|. |
|
51 | 51 | :type apiuser: AuthUser |
|
52 | 52 | :param repoid: Set the repository name or repository ID. |
|
53 | 53 | :type repoid: str or int |
|
54 | 54 | :param revision: Specify the revision for which to set a comment. |
|
55 | 55 | :type revision: str |
|
56 | 56 | :param message: The comment text. |
|
57 | 57 | :type message: str |
|
58 | 58 | :param userid: Set the user name of the comment creator. |
|
59 | 59 | :type userid: Optional(str or int) |
|
60 | 60 | :param status: Set the comment status. The following are valid options: |
|
61 | 61 | * not_reviewed |
|
62 | 62 | * approved |
|
63 | 63 | * rejected |
|
64 | 64 | * under_review |
|
65 | 65 | :type status: str |
|
66 | 66 | |
|
67 | 67 | Example error output: |
|
68 | 68 | |
|
69 | 69 | .. code-block:: javascript |
|
70 | 70 | |
|
71 | 71 | { |
|
72 | 72 | "id" : <id_given_in_input>, |
|
73 | 73 | "result" : { |
|
74 | 74 | "msg": "Commented on commit `<revision>` for repository `<repoid>`", |
|
75 | 75 | "status_change": null or <status>, |
|
76 | 76 | "success": true |
|
77 | 77 | }, |
|
78 | 78 | "error" : null |
|
79 | 79 | } |
|
80 | 80 | |
|
81 | 81 | """ |
|
82 | 82 | from .repo_api import comment_commit |
|
83 | 83 | |
|
84 | 84 | return comment_commit(request=request, |
|
85 | 85 | apiuser=apiuser, repoid=repoid, commit_id=revision, |
|
86 | 86 | message=message, userid=userid, status=status) |
|
87 | 87 | |
|
88 | 88 | |
|
89 | 89 | @jsonrpc_method() |
|
90 | 90 | @jsonrpc_deprecated_method( |
|
91 | 91 | use_method='get_ip', deprecated_at_version='4.0.0') |
|
92 | 92 | def show_ip(request, apiuser, userid=Optional(OAttr('apiuser'))): |
|
93 | 93 | from .server_api import get_ip |
|
94 | 94 | return get_ip(request=request, apiuser=apiuser, userid=userid) |
|
95 | 95 | |
|
96 | 96 | |
|
97 | 97 | @jsonrpc_method() |
|
98 | 98 | @jsonrpc_deprecated_method( |
|
99 | 99 | use_method='get_user_locks', deprecated_at_version='4.0.0') |
|
100 | 100 | def get_locks(request, apiuser, userid=Optional(OAttr('apiuser'))): |
|
101 | 101 | from .user_api import get_user_locks |
|
102 | 102 | return get_user_locks(request=request, apiuser=apiuser, userid=userid) No newline at end of file |
@@ -1,257 +1,257 b'' | |||
|
1 | 1 | |
|
2 | 2 | |
|
3 |
# Copyright (C) 2011-202 |
|
|
3 | # Copyright (C) 2011-2023 RhodeCode GmbH | |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | import logging |
|
23 | 23 | import time |
|
24 | 24 | |
|
25 | 25 | from rhodecode.api import jsonrpc_method, JSONRPCError |
|
26 | 26 | from rhodecode.api.exc import JSONRPCValidationError |
|
27 | 27 | from rhodecode.api.utils import ( |
|
28 | 28 | Optional, OAttr, get_gist_or_error, get_user_or_error, |
|
29 | 29 | has_superadmin_permission) |
|
30 | 30 | from rhodecode.model.db import Session, or_ |
|
31 | 31 | from rhodecode.model.gist import Gist, GistModel |
|
32 | 32 | |
|
33 | 33 | log = logging.getLogger(__name__) |
|
34 | 34 | |
|
35 | 35 | |
|
36 | 36 | @jsonrpc_method() |
|
37 | 37 | def get_gist(request, apiuser, gistid, content=Optional(False)): |
|
38 | 38 | """ |
|
39 | 39 | Get the specified gist, based on the gist ID. |
|
40 | 40 | |
|
41 | 41 | :param apiuser: This is filled automatically from the |authtoken|. |
|
42 | 42 | :type apiuser: AuthUser |
|
43 | 43 | :param gistid: Set the id of the private or public gist |
|
44 | 44 | :type gistid: str |
|
45 | 45 | :param content: Return the gist content. Default is false. |
|
46 | 46 | :type content: Optional(bool) |
|
47 | 47 | """ |
|
48 | 48 | |
|
49 | 49 | gist = get_gist_or_error(gistid) |
|
50 | 50 | content = Optional.extract(content) |
|
51 | 51 | |
|
52 | 52 | if not has_superadmin_permission(apiuser): |
|
53 | 53 | if gist.gist_owner != apiuser.user_id: |
|
54 | 54 | raise JSONRPCError('gist `%s` does not exist' % (gistid,)) |
|
55 | 55 | data = gist.get_api_data() |
|
56 | 56 | |
|
57 | 57 | if content: |
|
58 | 58 | from rhodecode.model.gist import GistModel |
|
59 | 59 | rev, gist_files = GistModel().get_gist_files(gistid) |
|
60 | 60 | data['content'] = dict([(x.path, x.str_content) for x in gist_files]) |
|
61 | 61 | return data |
|
62 | 62 | |
|
63 | 63 | |
|
64 | 64 | @jsonrpc_method() |
|
65 | 65 | def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))): |
|
66 | 66 | """ |
|
67 | 67 | Get all gists for given user. If userid is empty returned gists |
|
68 | 68 | are for user who called the api |
|
69 | 69 | |
|
70 | 70 | :param apiuser: This is filled automatically from the |authtoken|. |
|
71 | 71 | :type apiuser: AuthUser |
|
72 | 72 | :param userid: user to get gists for |
|
73 | 73 | :type userid: Optional(str or int) |
|
74 | 74 | """ |
|
75 | 75 | |
|
76 | 76 | if not has_superadmin_permission(apiuser): |
|
77 | 77 | # make sure normal user does not pass someone else userid, |
|
78 | 78 | # he is not allowed to do that |
|
79 | 79 | if not isinstance(userid, Optional) and userid != apiuser.user_id: |
|
80 | 80 | raise JSONRPCError( |
|
81 | 81 | 'userid is not the same as your user' |
|
82 | 82 | ) |
|
83 | 83 | |
|
84 | 84 | if isinstance(userid, Optional): |
|
85 | 85 | user_id = apiuser.user_id |
|
86 | 86 | else: |
|
87 | 87 | user_id = get_user_or_error(userid).user_id |
|
88 | 88 | |
|
89 | 89 | gists = [] |
|
90 | 90 | _gists = Gist().query() \ |
|
91 | 91 | .filter(or_( |
|
92 | 92 | Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \ |
|
93 | 93 | .filter(Gist.gist_owner == user_id) \ |
|
94 | 94 | .order_by(Gist.created_on.desc()) |
|
95 | 95 | for gist in _gists: |
|
96 | 96 | gists.append(gist.get_api_data()) |
|
97 | 97 | return gists |
|
98 | 98 | |
|
99 | 99 | |
|
100 | 100 | @jsonrpc_method() |
|
101 | 101 | def create_gist( |
|
102 | 102 | request, apiuser, files, gistid=Optional(None), |
|
103 | 103 | owner=Optional(OAttr('apiuser')), |
|
104 | 104 | gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1), |
|
105 | 105 | acl_level=Optional(Gist.ACL_LEVEL_PUBLIC), |
|
106 | 106 | description=Optional('')): |
|
107 | 107 | """ |
|
108 | 108 | Creates a new Gist. |
|
109 | 109 | |
|
110 | 110 | :param apiuser: This is filled automatically from the |authtoken|. |
|
111 | 111 | :type apiuser: AuthUser |
|
112 | 112 | :param files: files to be added to the gist. The data structure has |
|
113 | 113 | to match the following example:: |
|
114 | 114 | |
|
115 | 115 | {'filename1': {'content':'...'}, 'filename2': {'content':'...'}} |
|
116 | 116 | |
|
117 | 117 | :type files: dict |
|
118 | 118 | :param gistid: Set a custom id for the gist |
|
119 | 119 | :type gistid: Optional(str) |
|
120 | 120 | :param owner: Set the gist owner, defaults to api method caller |
|
121 | 121 | :type owner: Optional(str or int) |
|
122 | 122 | :param gist_type: type of gist ``public`` or ``private`` |
|
123 | 123 | :type gist_type: Optional(str) |
|
124 | 124 | :param lifetime: time in minutes of gist lifetime |
|
125 | 125 | :type lifetime: Optional(int) |
|
126 | 126 | :param acl_level: acl level for this gist, can be |
|
127 | 127 | ``acl_public`` or ``acl_private`` If the value is set to |
|
128 | 128 | ``acl_private`` only logged in users are able to access this gist. |
|
129 | 129 | If not set it defaults to ``acl_public``. |
|
130 | 130 | :type acl_level: Optional(str) |
|
131 | 131 | :param description: gist description |
|
132 | 132 | :type description: Optional(str) |
|
133 | 133 | |
|
134 | 134 | Example output: |
|
135 | 135 | |
|
136 | 136 | .. code-block:: bash |
|
137 | 137 | |
|
138 | 138 | id : <id_given_in_input> |
|
139 | 139 | result : { |
|
140 | 140 | "msg": "created new gist", |
|
141 | 141 | "gist": {} |
|
142 | 142 | } |
|
143 | 143 | error : null |
|
144 | 144 | |
|
145 | 145 | Example error output: |
|
146 | 146 | |
|
147 | 147 | .. code-block:: bash |
|
148 | 148 | |
|
149 | 149 | id : <id_given_in_input> |
|
150 | 150 | result : null |
|
151 | 151 | error : { |
|
152 | 152 | "failed to create gist" |
|
153 | 153 | } |
|
154 | 154 | |
|
155 | 155 | """ |
|
156 | 156 | from rhodecode.model import validation_schema |
|
157 | 157 | from rhodecode.model.validation_schema.schemas import gist_schema |
|
158 | 158 | |
|
159 | 159 | if isinstance(owner, Optional): |
|
160 | 160 | owner = apiuser.user_id |
|
161 | 161 | |
|
162 | 162 | owner = get_user_or_error(owner) |
|
163 | 163 | |
|
164 | 164 | lifetime = Optional.extract(lifetime) |
|
165 | 165 | schema = gist_schema.GistSchema().bind( |
|
166 | 166 | # bind the given values if it's allowed, however the deferred |
|
167 | 167 | # validator will still validate it according to other rules |
|
168 | 168 | lifetime_options=[lifetime]) |
|
169 | 169 | |
|
170 | 170 | try: |
|
171 | 171 | nodes = gist_schema.nodes_to_sequence( |
|
172 | 172 | files, colander_node=schema.get('nodes')) |
|
173 | 173 | |
|
174 | 174 | schema_data = schema.deserialize(dict( |
|
175 | 175 | gistid=Optional.extract(gistid), |
|
176 | 176 | description=Optional.extract(description), |
|
177 | 177 | gist_type=Optional.extract(gist_type), |
|
178 | 178 | lifetime=lifetime, |
|
179 | 179 | gist_acl_level=Optional.extract(acl_level), |
|
180 | 180 | nodes=nodes |
|
181 | 181 | )) |
|
182 | 182 | |
|
183 | 183 | # convert to safer format with just KEYs so we sure no duplicates |
|
184 | 184 | schema_data['nodes'] = gist_schema.sequence_to_nodes( |
|
185 | 185 | schema_data['nodes'], colander_node=schema.get('nodes')) |
|
186 | 186 | |
|
187 | 187 | except validation_schema.Invalid as err: |
|
188 | 188 | raise JSONRPCValidationError(colander_exc=err) |
|
189 | 189 | |
|
190 | 190 | try: |
|
191 | 191 | gist = GistModel().create( |
|
192 | 192 | owner=owner, |
|
193 | 193 | gist_id=schema_data['gistid'], |
|
194 | 194 | description=schema_data['description'], |
|
195 | 195 | gist_mapping=schema_data['nodes'], |
|
196 | 196 | gist_type=schema_data['gist_type'], |
|
197 | 197 | lifetime=schema_data['lifetime'], |
|
198 | 198 | gist_acl_level=schema_data['gist_acl_level']) |
|
199 | 199 | Session().commit() |
|
200 | 200 | return { |
|
201 | 201 | 'msg': 'created new gist', |
|
202 | 202 | 'gist': gist.get_api_data() |
|
203 | 203 | } |
|
204 | 204 | except Exception: |
|
205 | 205 | log.exception('Error occurred during creation of gist') |
|
206 | 206 | raise JSONRPCError('failed to create gist') |
|
207 | 207 | |
|
208 | 208 | |
|
209 | 209 | @jsonrpc_method() |
|
210 | 210 | def delete_gist(request, apiuser, gistid): |
|
211 | 211 | """ |
|
212 | 212 | Deletes existing gist |
|
213 | 213 | |
|
214 | 214 | :param apiuser: filled automatically from apikey |
|
215 | 215 | :type apiuser: AuthUser |
|
216 | 216 | :param gistid: id of gist to delete |
|
217 | 217 | :type gistid: str |
|
218 | 218 | |
|
219 | 219 | Example output: |
|
220 | 220 | |
|
221 | 221 | .. code-block:: bash |
|
222 | 222 | |
|
223 | 223 | id : <id_given_in_input> |
|
224 | 224 | result : { |
|
225 | 225 | "deleted gist ID: <gist_id>", |
|
226 | 226 | "gist": null |
|
227 | 227 | } |
|
228 | 228 | error : null |
|
229 | 229 | |
|
230 | 230 | Example error output: |
|
231 | 231 | |
|
232 | 232 | .. code-block:: bash |
|
233 | 233 | |
|
234 | 234 | id : <id_given_in_input> |
|
235 | 235 | result : null |
|
236 | 236 | error : { |
|
237 | 237 | "failed to delete gist ID:<gist_id>" |
|
238 | 238 | } |
|
239 | 239 | |
|
240 | 240 | """ |
|
241 | 241 | |
|
242 | 242 | gist = get_gist_or_error(gistid) |
|
243 | 243 | if not has_superadmin_permission(apiuser): |
|
244 | 244 | if gist.gist_owner != apiuser.user_id: |
|
245 | 245 | raise JSONRPCError('gist `%s` does not exist' % (gistid,)) |
|
246 | 246 | |
|
247 | 247 | try: |
|
248 | 248 | GistModel().delete(gist) |
|
249 | 249 | Session().commit() |
|
250 | 250 | return { |
|
251 | 251 | 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,), |
|
252 | 252 | 'gist': None |
|
253 | 253 | } |
|
254 | 254 | except Exception: |
|
255 | 255 | log.exception('Error occured during gist deletion') |
|
256 | 256 | raise JSONRPCError('failed to delete gist ID:%s' |
|
257 | 257 | % (gist.gist_access_id,)) No newline at end of file |
@@ -1,1113 +1,1113 b'' | |||
|
1 | 1 | |
|
2 | 2 | |
|
3 |
# Copyright (C) 2011-202 |
|
|
3 | # Copyright (C) 2011-2023 RhodeCode GmbH | |
|
4 | 4 | # |
|
5 | 5 | # This program is free software: you can redistribute it and/or modify |
|
6 | 6 | # it under the terms of the GNU Affero General Public License, version 3 |
|
7 | 7 | # (only), as published by the Free Software Foundation. |
|
8 | 8 | # |
|
9 | 9 | # This program is distributed in the hope that it will be useful, |
|
10 | 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 | 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 | 12 | # GNU General Public License for more details. |
|
13 | 13 | # |
|
14 | 14 | # You should have received a copy of the GNU Affero General Public License |
|
15 | 15 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 | 16 | # |
|
17 | 17 | # This program is dual-licensed. If you wish to learn more about the |
|
18 | 18 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
19 | 19 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
20 | 20 | |
|
21 | 21 | |
|
22 | 22 | import logging |
|
23 | 23 | |
|
24 | 24 | from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError |
|
25 | 25 | from rhodecode.api.utils import ( |
|
26 | 26 | has_superadmin_permission, Optional, OAttr, get_repo_or_error, |
|
27 | 27 | get_pull_request_or_error, get_commit_or_error, get_user_or_error, |
|
28 | 28 | validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions) |
|
29 | 29 | from rhodecode.lib import channelstream |
|
30 | 30 | from rhodecode.lib.auth import (HasRepoPermissionAnyApi) |
|
31 | 31 | from rhodecode.lib.base import vcs_operation_context |
|
32 | 32 | from rhodecode.lib.utils2 import str2bool |
|
33 | 33 | from rhodecode.lib.vcs.backends.base import unicode_to_reference |
|
34 | 34 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
35 | 35 | from rhodecode.model.comment import CommentsModel |
|
36 | 36 | from rhodecode.model.db import ( |
|
37 | 37 | Session, ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers) |
|
38 | 38 | from rhodecode.model.pull_request import PullRequestModel, MergeCheck |
|
39 | 39 | from rhodecode.model.settings import SettingsModel |
|
40 | 40 | from rhodecode.model.validation_schema import Invalid |
|
41 | 41 | from rhodecode.model.validation_schema.schemas.reviewer_schema import ReviewerListSchema |
|
42 | 42 | |
|
43 | 43 | log = logging.getLogger(__name__) |
|
44 | 44 | |
|
45 | 45 | |
|
46 | 46 | @jsonrpc_method() |
|
47 | 47 | def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None), |
|
48 | 48 | merge_state=Optional(False)): |
|
49 | 49 | """ |
|
50 | 50 | Get a pull request based on the given ID. |
|
51 | 51 | |
|
52 | 52 | :param apiuser: This is filled automatically from the |authtoken|. |
|
53 | 53 | :type apiuser: AuthUser |
|
54 | 54 | :param repoid: Optional, repository name or repository ID from where |
|
55 | 55 | the pull request was opened. |
|
56 | 56 | :type repoid: str or int |
|
57 | 57 | :param pullrequestid: ID of the requested pull request. |
|
58 | 58 | :type pullrequestid: int |
|
59 | 59 | :param merge_state: Optional calculate merge state for each repository. |
|
60 | 60 | This could result in longer time to fetch the data |
|
61 | 61 | :type merge_state: bool |
|
62 | 62 | |
|
63 | 63 | Example output: |
|
64 | 64 | |
|
65 | 65 | .. code-block:: bash |
|
66 | 66 | |
|
67 | 67 | "id": <id_given_in_input>, |
|
68 | 68 | "result": |
|
69 | 69 | { |
|
70 | 70 | "pull_request_id": "<pull_request_id>", |
|
71 | 71 | "url": "<url>", |
|
72 | 72 | "title": "<title>", |
|
73 | 73 | "description": "<description>", |
|
74 | 74 | "status" : "<status>", |
|
75 | 75 | "created_on": "<date_time_created>", |
|
76 | 76 | "updated_on": "<date_time_updated>", |
|
77 | 77 | "versions": "<number_or_versions_of_pr>", |
|
78 | 78 | "commit_ids": [ |
|
79 | 79 | ... |
|
80 | 80 | "<commit_id>", |
|
81 | 81 | "<commit_id>", |
|
82 | 82 | ... |
|
83 | 83 | ], |
|
84 | 84 | "review_status": "<review_status>", |
|
85 | 85 | "mergeable": { |
|
86 | 86 | "status": "<bool>", |
|
87 | 87 | "message": "<message>", |
|
88 | 88 | }, |
|
89 | 89 | "source": { |
|
90 | 90 | "clone_url": "<clone_url>", |
|
91 | 91 | "repository": "<repository_name>", |
|
92 | 92 | "reference": |
|
93 | 93 | { |
|
94 | 94 | "name": "<name>", |
|
95 | 95 | "type": "<type>", |
|
96 | 96 | "commit_id": "<commit_id>", |
|
97 | 97 | } |
|
98 | 98 | }, |
|
99 | 99 | "target": { |
|
100 | 100 | "clone_url": "<clone_url>", |
|
101 | 101 | "repository": "<repository_name>", |
|
102 | 102 | "reference": |
|
103 | 103 | { |
|
104 | 104 | "name": "<name>", |
|
105 | 105 | "type": "<type>", |
|
106 | 106 | "commit_id": "<commit_id>", |
|
107 | 107 | } |
|
108 | 108 | }, |
|
109 | 109 | "merge": { |
|
110 | 110 | "clone_url": "<clone_url>", |
|
111 | 111 | "reference": |
|
112 | 112 | { |
|
113 | 113 | "name": "<name>", |
|
114 | 114 | "type": "<type>", |
|
115 | 115 | "commit_id": "<commit_id>", |
|
116 | 116 | } |
|
117 | 117 | }, |
|
118 | 118 | "author": <user_obj>, |
|
119 | 119 | "reviewers": [ |
|
120 | 120 | ... |
|
121 | 121 | { |
|
122 | 122 | "user": "<user_obj>", |
|
123 | 123 | "review_status": "<review_status>", |
|
124 | 124 | } |
|
125 | 125 | ... |
|
126 | 126 | ] |
|
127 | 127 | }, |
|
128 | 128 | "error": null |
|
129 | 129 | """ |
|
130 | 130 | |
|
131 | 131 | pull_request = get_pull_request_or_error(pullrequestid) |
|
132 | 132 | if Optional.extract(repoid): |
|
133 | 133 | repo = get_repo_or_error(repoid) |
|
134 | 134 | else: |
|
135 | 135 | repo = pull_request.target_repo |
|
136 | 136 | |
|
137 | 137 | if not PullRequestModel().check_user_read(pull_request, apiuser, api=True): |
|
138 | 138 | raise JSONRPCError('repository `%s` or pull request `%s` ' |
|
139 | 139 | 'does not exist' % (repoid, pullrequestid)) |
|
140 | 140 | |
|
141 | 141 | # NOTE(marcink): only calculate and return merge state if the pr state is 'created' |
|
142 | 142 | # otherwise we can lock the repo on calculation of merge state while update/merge |
|
143 | 143 | # is happening. |
|
144 | 144 | pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED |
|
145 | 145 | merge_state = Optional.extract(merge_state, binary=True) and pr_created |
|
146 | 146 | data = pull_request.get_api_data(with_merge_state=merge_state) |
|
147 | 147 | return data |
|
148 | 148 | |
|
149 | 149 | |
|
150 | 150 | @jsonrpc_method() |
|
151 | 151 | def get_pull_requests(request, apiuser, repoid, status=Optional('new'), |
|
152 | 152 | merge_state=Optional(False)): |
|
153 | 153 | """ |
|
154 | 154 | Get all pull requests from the repository specified in `repoid`. |
|
155 | 155 | |
|
156 | 156 | :param apiuser: This is filled automatically from the |authtoken|. |
|
157 | 157 | :type apiuser: AuthUser |
|
158 | 158 | :param repoid: Optional repository name or repository ID. |
|
159 | 159 | :type repoid: str or int |
|
160 | 160 | :param status: Only return pull requests with the specified status. |
|
161 | 161 | Valid options are. |
|
162 | 162 | * ``new`` (default) |
|
163 | 163 | * ``open`` |
|
164 | 164 | * ``closed`` |
|
165 | 165 | :type status: str |
|
166 | 166 | :param merge_state: Optional calculate merge state for each repository. |
|
167 | 167 | This could result in longer time to fetch the data |
|
168 | 168 | :type merge_state: bool |
|
169 | 169 | |
|
170 | 170 | Example output: |
|
171 | 171 | |
|
172 | 172 | .. code-block:: bash |
|
173 | 173 | |
|
174 | 174 | "id": <id_given_in_input>, |
|
175 | 175 | "result": |
|
176 | 176 | [ |
|
177 | 177 | ... |
|
178 | 178 | { |
|
179 | 179 | "pull_request_id": "<pull_request_id>", |
|
180 | 180 | "url": "<url>", |
|
181 | 181 | "title" : "<title>", |
|
182 | 182 | "description": "<description>", |
|
183 | 183 | "status": "<status>", |
|
184 | 184 | "created_on": "<date_time_created>", |
|
185 | 185 | "updated_on": "<date_time_updated>", |
|
186 | 186 | "commit_ids": [ |
|
187 | 187 | ... |
|
188 | 188 | "<commit_id>", |
|
189 | 189 | "<commit_id>", |
|
190 | 190 | ... |
|
191 | 191 | ], |
|
192 | 192 | "review_status": "<review_status>", |
|
193 | 193 | "mergeable": { |
|
194 | 194 | "status": "<bool>", |
|
195 | 195 | "message: "<message>", |
|
196 | 196 | }, |
|
197 | 197 | "source": { |
|
198 | 198 | "clone_url": "<clone_url>", |
|
199 | 199 | "reference": |
|
200 | 200 | { |
|
201 | 201 | "name": "<name>", |
|
202 | 202 | "type": "<type>", |
|
203 | 203 | "commit_id": "<commit_id>", |
|
204 | 204 | } |
|
205 | 205 | }, |
|
206 | 206 | "target": { |
|
207 | 207 | "clone_url": "<clone_url>", |
|
208 | 208 | "reference": |
|
209 | 209 | { |
|
210 | 210 | "name": "<name>", |
|
211 | 211 | "type": "<type>", |
|
212 | 212 | "commit_id": "<commit_id>", |
|
213 | 213 | } |
|
214 | 214 | }, |
|
215 | 215 | "merge": { |
|
216 | 216 | "clone_url": "<clone_url>", |
|
217 | 217 | "reference": |
|
218 | 218 | { |
|
219 | 219 | "name": "<name>", |
|
220 | 220 | "type": "<type>", |
|
221 | 221 | "commit_id": "<commit_id>", |
|
222 | 222 | } |
|
223 | 223 | }, |
|
224 | 224 | "author": <user_obj>, |
|
225 | 225 | "reviewers": [ |
|
226 | 226 | ... |
|
227 | 227 | { |
|
228 | 228 | "user": "<user_obj>", |
|
229 | 229 | "review_status": "<review_status>", |
|
230 | 230 | } |
|
231 | 231 | ... |
|
232 | 232 | ] |
|
233 | 233 | } |
|
234 | 234 | ... |
|
235 | 235 | ], |
|
236 | 236 | "error": null |
|
237 | 237 | |
|
238 | 238 | """ |
|
239 | 239 | repo = get_repo_or_error(repoid) |
|
240 | 240 | if not has_superadmin_permission(apiuser): |
|
241 | 241 | _perms = ( |
|
242 | 242 | 'repository.admin', 'repository.write', 'repository.read',) |
|
243 | 243 | validate_repo_permissions(apiuser, repoid, repo, _perms) |
|
244 | 244 | |
|
245 | 245 | status = Optional.extract(status) |
|
246 | 246 | merge_state = Optional.extract(merge_state, binary=True) |
|
247 | 247 | pull_requests = PullRequestModel().get_all(repo, statuses=[status], |
|
248 | 248 | order_by='id', order_dir='desc') |
|
249 | 249 | data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests] |
|
250 | 250 | return data |
|
251 | 251 | |
|
252 | 252 | |
|
253 | 253 | @jsonrpc_method() |
|
254 | 254 | def merge_pull_request( |
|
255 | 255 | request, apiuser, pullrequestid, repoid=Optional(None), |
|
256 | 256 | userid=Optional(OAttr('apiuser'))): |
|
257 | 257 | """ |
|
258 | 258 | Merge the pull request specified by `pullrequestid` into its target |
|
259 | 259 | repository. |
|
260 | 260 | |
|
261 | 261 | :param apiuser: This is filled automatically from the |authtoken|. |
|
262 | 262 | :type apiuser: AuthUser |
|
263 | 263 | :param repoid: Optional, repository name or repository ID of the |
|
264 | 264 | target repository to which the |pr| is to be merged. |
|
265 | 265 | :type repoid: str or int |
|
266 | 266 | :param pullrequestid: ID of the pull request which shall be merged. |
|
267 | 267 | :type pullrequestid: int |
|
268 | 268 | :param userid: Merge the pull request as this user. |
|
269 | 269 | :type userid: Optional(str or int) |
|
270 | 270 | |
|
271 | 271 | Example output: |
|
272 | 272 | |
|
273 | 273 | .. code-block:: bash |
|
274 | 274 | |
|
275 | 275 | "id": <id_given_in_input>, |
|
276 | 276 | "result": { |
|
277 | 277 | "executed": "<bool>", |
|
278 | 278 | "failure_reason": "<int>", |
|
279 | 279 | "merge_status_message": "<str>", |
|
280 | 280 | "merge_commit_id": "<merge_commit_id>", |
|
281 | 281 | "possible": "<bool>", |
|
282 | 282 | "merge_ref": { |
|
283 | 283 | "commit_id": "<commit_id>", |
|
284 | 284 | "type": "<type>", |
|
285 | 285 | "name": "<name>" |
|
286 | 286 | } |
|
287 | 287 | }, |
|
288 | 288 | "error": null |
|
289 | 289 | """ |
|
290 | 290 | pull_request = get_pull_request_or_error(pullrequestid) |
|
291 | 291 | if Optional.extract(repoid): |
|
292 | 292 | repo = get_repo_or_error(repoid) |
|
293 | 293 | else: |
|
294 | 294 | repo = pull_request.target_repo |
|
295 | 295 | auth_user = apiuser |
|
296 | 296 | |
|
297 | 297 | if not isinstance(userid, Optional): |
|
298 | 298 | is_repo_admin = HasRepoPermissionAnyApi('repository.admin')( |
|
299 | 299 | user=apiuser, repo_name=repo.repo_name) |
|
300 | 300 | if has_superadmin_permission(apiuser) or is_repo_admin: |
|
301 | 301 | apiuser = get_user_or_error(userid) |
|
302 | 302 | auth_user = apiuser.AuthUser() |
|
303 | 303 | else: |
|
304 | 304 | raise JSONRPCError('userid is not the same as your user') |
|
305 | 305 | |
|
306 | 306 | if pull_request.pull_request_state != PullRequest.STATE_CREATED: |
|
307 | 307 | raise JSONRPCError( |
|
308 | 308 | 'Operation forbidden because pull request is in state {}, ' |
|
309 | 309 | 'only state {} is allowed.'.format( |
|
310 | 310 | pull_request.pull_request_state, PullRequest.STATE_CREATED)) |
|
311 | 311 | |
|
312 | 312 | with pull_request.set_state(PullRequest.STATE_UPDATING): |
|
313 | 313 | check = MergeCheck.validate(pull_request, auth_user=auth_user, |
|
314 | 314 | translator=request.translate) |
|
315 | 315 | merge_possible = not check.failed |
|
316 | 316 | |
|
317 | 317 | if not merge_possible: |
|
318 | 318 | error_messages = [] |
|
319 | 319 | for err_type, error_msg in check.errors: |
|
320 | 320 | error_msg = request.translate(error_msg) |
|
321 | 321 | error_messages.append(error_msg) |
|
322 | 322 | |
|
323 | 323 | reasons = ','.join(error_messages) |
|
324 | 324 | raise JSONRPCError( |
|
325 | 325 | 'merge not possible for following reasons: {}'.format(reasons)) |
|
326 | 326 | |
|
327 | 327 | target_repo = pull_request.target_repo |
|
328 | 328 | extras = vcs_operation_context( |
|
329 | 329 | request.environ, repo_name=target_repo.repo_name, |
|
330 | 330 | username=auth_user.username, action='push', |
|
331 | 331 | scm=target_repo.repo_type) |
|
332 | 332 | with pull_request.set_state(PullRequest.STATE_UPDATING): |
|
333 | 333 | merge_response = PullRequestModel().merge_repo( |
|
334 | 334 | pull_request, apiuser, extras=extras) |
|
335 | 335 | if merge_response.executed: |
|
336 | 336 | PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user) |
|
337 | 337 | |
|
338 | 338 | Session().commit() |
|
339 | 339 | |
|
340 | 340 | # In previous versions the merge response directly contained the merge |
|
341 | 341 | # commit id. It is now contained in the merge reference object. To be |
|
342 | 342 | # backwards compatible we have to extract it again. |
|
343 | 343 | merge_response = merge_response.asdict() |
|
344 | 344 | merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id |
|
345 | 345 | |
|
346 | 346 | return merge_response |
|
347 | 347 | |
|
348 | 348 | |
|
349 | 349 | @jsonrpc_method() |
|
350 | 350 | def get_pull_request_comments( |
|
351 | 351 | request, apiuser, pullrequestid, repoid=Optional(None)): |
|
352 | 352 | """ |
|
353 | 353 | Get all comments of pull request specified with the `pullrequestid` |
|
354 | 354 | |
|
355 | 355 | :param apiuser: This is filled automatically from the |authtoken|. |
|
356 | 356 | :type apiuser: AuthUser |
|
357 | 357 | :param repoid: Optional repository name or repository ID. |
|
358 | 358 | :type repoid: str or int |
|
359 | 359 | :param pullrequestid: The pull request ID. |
|
360 | 360 | :type pullrequestid: int |
|
361 | 361 | |
|
362 | 362 | Example output: |
|
363 | 363 | |
|
364 | 364 | .. code-block:: bash |
|
365 | 365 | |
|
366 | 366 | id : <id_given_in_input> |
|
367 | 367 | result : [ |
|
368 | 368 | { |
|
369 | 369 | "comment_author": { |
|
370 | 370 | "active": true, |
|
371 | 371 | "full_name_or_username": "Tom Gore", |
|
372 | 372 | "username": "admin" |
|
373 | 373 | }, |
|
374 | 374 | "comment_created_on": "2017-01-02T18:43:45.533", |
|
375 | 375 | "comment_f_path": null, |
|
376 | 376 | "comment_id": 25, |
|
377 | 377 | "comment_lineno": null, |
|
378 | 378 | "comment_status": { |
|
379 | 379 | "status": "under_review", |
|
380 | 380 | "status_lbl": "Under Review" |
|
381 | 381 | }, |
|
382 | 382 | "comment_text": "Example text", |
|
383 | 383 | "comment_type": null, |
|
384 | 384 | "comment_last_version: 0, |
|
385 | 385 | "pull_request_version": null, |
|
386 | 386 | "comment_commit_id": None, |
|
387 | 387 | "comment_pull_request_id": <pull_request_id> |
|
388 | 388 | } |
|
389 | 389 | ], |
|
390 | 390 | error : null |
|
391 | 391 | """ |
|
392 | 392 | |
|
393 | 393 | pull_request = get_pull_request_or_error(pullrequestid) |
|
394 | 394 | if Optional.extract(repoid): |
|
395 | 395 | repo = get_repo_or_error(repoid) |
|
396 | 396 | else: |
|
397 | 397 | repo = pull_request.target_repo |
|
398 | 398 | |
|
399 | 399 | if not PullRequestModel().check_user_read( |
|
400 | 400 | pull_request, apiuser, api=True): |
|
401 | 401 | raise JSONRPCError('repository `%s` or pull request `%s` ' |
|
402 | 402 | 'does not exist' % (repoid, pullrequestid)) |
|
403 | 403 | |
|
404 | 404 | (pull_request_latest, |
|
405 | 405 | pull_request_at_ver, |
|
406 | 406 | pull_request_display_obj, |
|
407 | 407 | at_version) = PullRequestModel().get_pr_version( |
|
408 | 408 | pull_request.pull_request_id, version=None) |
|
409 | 409 | |
|
410 | 410 | versions = pull_request_display_obj.versions() |
|
411 | 411 | ver_map = { |
|
412 | 412 | ver.pull_request_version_id: cnt |
|
413 | 413 | for cnt, ver in enumerate(versions, 1) |
|
414 | 414 | } |
|
415 | 415 | |
|
416 | 416 | # GENERAL COMMENTS with versions # |
|
417 | 417 | q = CommentsModel()._all_general_comments_of_pull_request(pull_request) |
|
418 | 418 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
419 | 419 | general_comments = q.all() |
|
420 | 420 | |
|
421 | 421 | # INLINE COMMENTS with versions # |
|
422 | 422 | q = CommentsModel()._all_inline_comments_of_pull_request(pull_request) |
|
423 | 423 | q = q.order_by(ChangesetComment.comment_id.asc()) |
|
424 | 424 | inline_comments = q.all() |
|
425 | 425 | |
|
426 | 426 | data = [] |
|
427 | 427 | for comment in inline_comments + general_comments: |
|
428 | 428 | full_data = comment.get_api_data() |
|
429 | 429 | pr_version_id = None |
|
430 | 430 | if comment.pull_request_version_id: |
|
431 | 431 | pr_version_id = 'v{}'.format( |
|
432 | 432 | ver_map[comment.pull_request_version_id]) |
|
433 | 433 | |
|
434 | 434 | # sanitize some entries |
|
435 | 435 | |
|
436 | 436 | full_data['pull_request_version'] = pr_version_id |
|
437 | 437 | full_data['comment_author'] = { |
|
438 | 438 | 'username': full_data['comment_author'].username, |
|
439 | 439 | 'full_name_or_username': full_data['comment_author'].full_name_or_username, |
|
440 | 440 | 'active': full_data['comment_author'].active, |
|
441 | 441 | } |
|
442 | 442 | |
|
443 | 443 | if full_data['comment_status']: |
|
444 | 444 | full_data['comment_status'] = { |
|
445 | 445 | 'status': full_data['comment_status'][0].status, |
|
446 | 446 | 'status_lbl': full_data['comment_status'][0].status_lbl, |
|
447 | 447 | } |
|
448 | 448 | else: |
|
449 | 449 | full_data['comment_status'] = {} |
|
450 | 450 | |
|
451 | 451 | data.append(full_data) |
|
452 | 452 | return data |
|
453 | 453 | |
|
454 | 454 | |
|
455 | 455 | @jsonrpc_method() |
|
456 | 456 | def comment_pull_request( |
|
457 | 457 | request, apiuser, pullrequestid, repoid=Optional(None), |
|
458 | 458 | message=Optional(None), commit_id=Optional(None), status=Optional(None), |
|
459 | 459 | comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE), |
|
460 | 460 | resolves_comment_id=Optional(None), extra_recipients=Optional([]), |
|
461 | 461 | userid=Optional(OAttr('apiuser')), send_email=Optional(True)): |
|
462 | 462 | """ |
|
463 | 463 | Comment on the pull request specified with the `pullrequestid`, |
|
464 | 464 | in the |repo| specified by the `repoid`, and optionally change the |
|
465 | 465 | review status. |
|
466 | 466 | |
|
467 | 467 | :param apiuser: This is filled automatically from the |authtoken|. |
|
468 | 468 | :type apiuser: AuthUser |
|
469 | 469 | :param repoid: Optional repository name or repository ID. |
|
470 | 470 | :type repoid: str or int |
|
471 | 471 | :param pullrequestid: The pull request ID. |
|
472 | 472 | :type pullrequestid: int |
|
473 | 473 | :param commit_id: Specify the commit_id for which to set a comment. If |
|
474 | 474 | given commit_id is different than latest in the PR status |
|
475 | 475 | change won't be performed. |
|
476 | 476 | :type commit_id: str |
|
477 | 477 | :param message: The text content of the comment. |
|
478 | 478 | :type message: str |
|
479 | 479 | :param status: (**Optional**) Set the approval status of the pull |
|
480 | 480 | request. One of: 'not_reviewed', 'approved', 'rejected', |
|
481 | 481 | 'under_review' |
|
482 | 482 | :type status: str |
|
483 | 483 | :param comment_type: Comment type, one of: 'note', 'todo' |
|
484 | 484 | :type comment_type: Optional(str), default: 'note' |
|
485 | 485 | :param resolves_comment_id: id of comment which this one will resolve |
|
486 | 486 | :type resolves_comment_id: Optional(int) |
|
487 | 487 | :param extra_recipients: list of user ids or usernames to add |
|
488 | 488 | notifications for this comment. Acts like a CC for notification |
|
489 | 489 | :type extra_recipients: Optional(list) |
|
490 | 490 | :param userid: Comment on the pull request as this user |
|
491 | 491 | :type userid: Optional(str or int) |
|
492 | 492 | :param send_email: Define if this comment should also send email notification |
|
493 | 493 | :type send_email: Optional(bool) |
|
494 | 494 | |
|
495 | 495 | Example output: |
|
496 | 496 | |
|
497 | 497 | .. code-block:: bash |
|
498 | 498 | |
|
499 | 499 | id : <id_given_in_input> |
|
500 | 500 | result : { |
|
501 | 501 | "pull_request_id": "<Integer>", |
|
502 | 502 | "comment_id": "<Integer>", |
|
503 | 503 | "status": {"given": <given_status>, |
|
504 | 504 | "was_changed": <bool status_was_actually_changed> }, |
|
505 | 505 | }, |
|
506 | 506 | error : null |
|
507 | 507 | """ |
|
508 | 508 | _ = request.translate |
|
509 | 509 | |
|
510 | 510 | pull_request = get_pull_request_or_error(pullrequestid) |
|
511 | 511 | if Optional.extract(repoid): |
|
512 | 512 | repo = get_repo_or_error(repoid) |
|
513 | 513 | else: |
|
514 | 514 | repo = pull_request.target_repo |
|
515 | 515 | |
|
516 | 516 | db_repo_name = repo.repo_name |
|
517 | 517 | auth_user = apiuser |
|
518 | 518 | if not isinstance(userid, Optional): |
|
519 | 519 | is_repo_admin = HasRepoPermissionAnyApi('repository.admin')( |
|
520 | 520 | user=apiuser, repo_name=db_repo_name) |
|
521 | 521 | if has_superadmin_permission(apiuser) or is_repo_admin: |
|
522 | 522 | apiuser = get_user_or_error(userid) |
|
523 | 523 | auth_user = apiuser.AuthUser() |
|
524 | 524 | else: |
|
525 | 525 | raise JSONRPCError('userid is not the same as your user') |
|
526 | 526 | |
|
527 | 527 | if pull_request.is_closed(): |
|
528 | 528 | raise JSONRPCError(f'pull request `{pullrequestid}` comment failed, pull request is closed') |
|
529 | 529 | |
|
530 | 530 | if not PullRequestModel().check_user_read( |
|
531 | 531 | pull_request, apiuser, api=True): |
|
532 | 532 | raise JSONRPCError('repository `%s` does not exist' % (repoid,)) |
|
533 | 533 | message = Optional.extract(message) |
|
534 | 534 | status = Optional.extract(status) |
|
535 | 535 | commit_id = Optional.extract(commit_id) |
|
536 | 536 | comment_type = Optional.extract(comment_type) |
|
537 | 537 | resolves_comment_id = Optional.extract(resolves_comment_id) |
|
538 | 538 | extra_recipients = Optional.extract(extra_recipients) |
|
539 | 539 | send_email = Optional.extract(send_email, binary=True) |
|
540 | 540 | |
|
541 | 541 | if not message and not status: |
|
542 | 542 | raise JSONRPCError( |
|
543 | 543 | 'Both message and status parameters are missing. ' |
|
544 | 544 | 'At least one is required.') |
|
545 | 545 | |
|
546 | 546 | if status and status not in (st[0] for st in ChangesetStatus.STATUSES): |
|
547 | 547 | raise JSONRPCError(f'Unknown comment status: `{status}`') |
|
548 | 548 | |
|
549 | 549 | if commit_id and commit_id not in pull_request.revisions: |
|
550 | 550 | raise JSONRPCError(f'Invalid commit_id `{commit_id}` for this pull request.') |
|
551 | 551 | |
|
552 | 552 | allowed_to_change_status = PullRequestModel().check_user_change_status( |
|
553 | 553 | pull_request, apiuser) |
|
554 | 554 | |
|
555 | 555 | # if commit_id is passed re-validated if user is allowed to change status |
|
556 | 556 | # based on the latest commit_id from the PR |
|
557 | 557 | if commit_id: |
|
558 | 558 | commit_idx = pull_request.revisions.index(commit_id) |
|
559 | 559 | if commit_idx != 0: |
|
560 | 560 | log.warning('Resetting allowed_to_change_status = False because commit is NOT the latest in pull-request') |
|
561 | 561 | allowed_to_change_status = False |
|
562 | 562 | |
|
563 | 563 | if resolves_comment_id: |
|
564 | 564 | comment = ChangesetComment.get(resolves_comment_id) |
|
565 | 565 | if not comment: |
|
566 | 566 | raise JSONRPCError(f'Invalid resolves_comment_id `{resolves_comment_id}` for this pull request.') |
|
567 | 567 | if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO: |
|
568 | 568 | raise JSONRPCError(f'Comment `{resolves_comment_id}` is wrong type for setting status to resolved.') |
|
569 | 569 | |
|
570 | 570 | text = message |
|
571 | 571 | status_label = ChangesetStatus.get_status_lbl(status) |
|
572 | 572 | if status and allowed_to_change_status: |
|
573 | 573 | st_message = ('Status change %(transition_icon)s %(status)s' |
|
574 | 574 | % {'transition_icon': '>', 'status': status_label}) |
|
575 | 575 | text = message or st_message |
|
576 | 576 | |
|
577 | 577 | rc_config = SettingsModel().get_all_settings() |
|
578 | 578 | renderer = rc_config.get('rhodecode_markup_renderer', 'rst') |
|
579 | 579 | |
|
580 | 580 | status_change = status and allowed_to_change_status |
|
581 | 581 | comment = CommentsModel().create( |
|
582 | 582 | text=text, |
|
583 | 583 | repo=pull_request.target_repo.repo_id, |
|
584 | 584 | user=apiuser.user_id, |
|
585 | 585 | pull_request=pull_request.pull_request_id, |
|
586 | 586 | f_path=None, |
|
587 | 587 | line_no=None, |
|
588 | 588 | status_change=(status_label if status_change else None), |
|
589 | 589 | status_change_type=(status if status_change else None), |
|
590 | 590 | closing_pr=False, |
|
591 | 591 | renderer=renderer, |
|
592 | 592 | comment_type=comment_type, |
|
593 | 593 | resolves_comment_id=resolves_comment_id, |
|
594 | 594 | auth_user=auth_user, |
|
595 | 595 | extra_recipients=extra_recipients, |
|
596 | 596 | send_email=send_email |
|
597 | 597 | ) |
|
598 | 598 | |
|
599 | 599 | is_inline = comment.is_inline |
|
600 | 600 | |
|
601 | 601 | if allowed_to_change_status and status: |
|
602 | 602 | old_calculated_status = pull_request.calculated_review_status() |
|
603 | 603 | ChangesetStatusModel().set_status( |
|
604 | 604 | pull_request.target_repo.repo_id, |
|
605 | 605 | status, |
|
606 | 606 | apiuser.user_id, |
|
607 | 607 | comment, |
|
608 | 608 | pull_request=pull_request.pull_request_id |
|
609 | 609 | ) |
|
610 | 610 | Session().flush() |
|
611 | 611 | |
|
612 | 612 | Session().commit() |
|
613 | 613 | |
|
614 | 614 | PullRequestModel().trigger_pull_request_hook( |
|
615 | 615 | pull_request, apiuser, 'comment', |
|
616 | 616 | data={'comment': comment}) |
|
617 | 617 | |
|
618 | 618 | if allowed_to_change_status and status: |
|
619 | 619 | # we now calculate the status of pull request, and based on that |
|
620 | 620 | # calculation we set the commits status |
|
621 | 621 | calculated_status = pull_request.calculated_review_status() |
|
622 | 622 | if old_calculated_status != calculated_status: |
|
623 | 623 | PullRequestModel().trigger_pull_request_hook( |
|
624 | 624 | pull_request, apiuser, 'review_status_change', |
|
625 | 625 | data={'status': calculated_status}) |
|
626 | 626 | |
|
627 | 627 | data = { |
|
628 | 628 | 'pull_request_id': pull_request.pull_request_id, |
|
629 | 629 | 'comment_id': comment.comment_id if comment else None, |
|
630 | 630 | 'status': {'given': status, 'was_changed': status_change}, |
|
631 | 631 | } |
|
632 | 632 | |
|
633 | 633 | comment_broadcast_channel = channelstream.comment_channel( |
|
634 | 634 | db_repo_name, pull_request_obj=pull_request) |
|
635 | 635 | |
|
636 | 636 | comment_data = data |
|
637 | 637 | comment_type = 'inline' if is_inline else 'general' |
|
638 | 638 | channelstream.comment_channelstream_push( |
|
639 | 639 | request, comment_broadcast_channel, apiuser, |
|
640 | 640 | _('posted a new {} comment').format(comment_type), |
|
641 | 641 | comment_data=comment_data) |
|
642 | 642 | |
|
643 | 643 | return data |
|
644 | 644 | |
|
645 | 645 | |
|
646 | 646 | def _reviewers_validation(obj_list): |
|
647 | 647 | schema = ReviewerListSchema() |
|
648 | 648 | try: |
|
649 | 649 | reviewer_objects = schema.deserialize(obj_list) |
|
650 | 650 | except Invalid as err: |
|
651 | 651 | raise JSONRPCValidationError(colander_exc=err) |
|
652 | 652 | |
|
653 | 653 | # validate users |
|
654 | 654 | for reviewer_object in reviewer_objects: |
|
655 | 655 | user = get_user_or_error(reviewer_object['username']) |
|
656 | 656 | reviewer_object['user_id'] = user.user_id |
|
657 | 657 | return reviewer_objects |
|
658 | 658 | |
|
659 | 659 | |
|
660 | 660 | @jsonrpc_method() |
|
661 | 661 | def create_pull_request( |
|
662 | 662 | request, apiuser, source_repo, target_repo, source_ref, target_ref, |
|
663 | 663 | owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''), |
|
664 | 664 | description_renderer=Optional(''), |
|
665 | 665 | reviewers=Optional(None), observers=Optional(None)): |
|
666 | 666 | """ |
|
667 | 667 | Creates a new pull request. |
|
668 | 668 | |
|
669 | 669 | Accepts refs in the following formats: |
|
670 | 670 | |
|
671 | 671 | * branch:<branch_name>:<sha> |
|
672 | 672 | * branch:<branch_name> |
|
673 | 673 | * bookmark:<bookmark_name>:<sha> (Mercurial only) |
|
674 | 674 | * bookmark:<bookmark_name> (Mercurial only) |
|
675 | 675 | |
|
676 | 676 | :param apiuser: This is filled automatically from the |authtoken|. |
|
677 | 677 | :type apiuser: AuthUser |
|
678 | 678 | :param source_repo: Set the source repository name. |
|
679 | 679 | :type source_repo: str |
|
680 | 680 | :param target_repo: Set the target repository name. |
|
681 | 681 | :type target_repo: str |
|
682 | 682 | :param source_ref: Set the source ref name. |
|
683 | 683 | :type source_ref: str |
|
684 | 684 | :param target_ref: Set the target ref name. |
|
685 | 685 | :type target_ref: str |
|
686 | 686 | :param owner: user_id or username |
|
687 | 687 | :type owner: Optional(str) |
|
688 | 688 | :param title: Optionally Set the pull request title, it's generated otherwise |
|
689 | 689 | :type title: str |
|
690 | 690 | :param description: Set the pull request description. |
|
691 | 691 | :type description: Optional(str) |
|
692 | 692 | :type description_renderer: Optional(str) |
|
693 | 693 | :param description_renderer: Set pull request renderer for the description. |
|
694 | 694 | It should be 'rst', 'markdown' or 'plain'. If not give default |
|
695 | 695 | system renderer will be used |
|
696 | 696 | :param reviewers: Set the new pull request reviewers list. |
|
697 | 697 | Reviewer defined by review rules will be added automatically to the |
|
698 | 698 | defined list. |
|
699 | 699 | :type reviewers: Optional(list) |
|
700 | 700 | Accepts username strings or objects of the format: |
|
701 | 701 | |
|
702 | 702 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
703 | 703 | :param observers: Set the new pull request observers list. |
|
704 | 704 | Reviewer defined by review rules will be added automatically to the |
|
705 | 705 | defined list. This feature is only available in RhodeCode EE |
|
706 | 706 | :type observers: Optional(list) |
|
707 | 707 | Accepts username strings or objects of the format: |
|
708 | 708 | |
|
709 | 709 | [{'username': 'nick', 'reasons': ['original author']}] |
|
710 | 710 | """ |
|
711 | 711 | |
|
712 | 712 | source_db_repo = get_repo_or_error(source_repo) |
|
713 | 713 | target_db_repo = get_repo_or_error(target_repo) |
|
714 | 714 | if not has_superadmin_permission(apiuser): |
|
715 | 715 | _perms = ('repository.admin', 'repository.write', 'repository.read',) |
|
716 | 716 | validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms) |
|
717 | 717 | |
|
718 | 718 | owner = validate_set_owner_permissions(apiuser, owner) |
|
719 | 719 | |
|
720 | 720 | full_source_ref = resolve_ref_or_error(source_ref, source_db_repo) |
|
721 | 721 | full_target_ref = resolve_ref_or_error(target_ref, target_db_repo) |
|
722 | 722 | |
|
723 | 723 | get_commit_or_error(full_source_ref, source_db_repo) |
|
724 | 724 | get_commit_or_error(full_target_ref, target_db_repo) |
|
725 | 725 | |
|
726 | 726 | reviewer_objects = Optional.extract(reviewers) or [] |
|
727 | 727 | observer_objects = Optional.extract(observers) or [] |
|
728 | 728 | |
|
729 | 729 | # serialize and validate passed in given reviewers |
|
730 | 730 | if reviewer_objects: |
|
731 | 731 | reviewer_objects = _reviewers_validation(reviewer_objects) |
|
732 | 732 | |
|
733 | 733 | if observer_objects: |
|
734 | 734 | observer_objects = _reviewers_validation(reviewer_objects) |
|
735 | 735 | |
|
736 | 736 | get_default_reviewers_data, validate_default_reviewers, validate_observers = \ |
|
737 | 737 | PullRequestModel().get_reviewer_functions() |
|
738 | 738 | |
|
739 | 739 | source_ref_obj = unicode_to_reference(full_source_ref) |
|
740 | 740 | target_ref_obj = unicode_to_reference(full_target_ref) |
|
741 | 741 | |
|
742 | 742 | # recalculate reviewers logic, to make sure we can validate this |
|
743 | 743 | default_reviewers_data = get_default_reviewers_data( |
|
744 | 744 | owner, |
|
745 | 745 | source_db_repo, |
|
746 | 746 | source_ref_obj, |
|
747 | 747 | target_db_repo, |
|
748 | 748 | target_ref_obj, |
|
749 | 749 | ) |
|
750 | 750 | |
|
751 | 751 | # now MERGE our given with the calculated from the default rules |
|
752 | 752 | just_reviewers = [ |
|
753 | 753 | x for x in default_reviewers_data['reviewers'] |
|
754 | 754 | if x['role'] == PullRequestReviewers.ROLE_REVIEWER] |
|
755 | 755 | reviewer_objects = just_reviewers + reviewer_objects |
|
756 | 756 | |
|
757 | 757 | try: |
|
758 | 758 | reviewers = validate_default_reviewers( |
|
759 | 759 | reviewer_objects, default_reviewers_data) |
|
760 | 760 | except ValueError as e: |
|
761 | 761 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) |
|
762 | 762 | |
|
763 | 763 | # now MERGE our given with the calculated from the default rules |
|
764 | 764 | just_observers = [ |
|
765 | 765 | x for x in default_reviewers_data['reviewers'] |
|
766 | 766 | if x['role'] == PullRequestReviewers.ROLE_OBSERVER] |
|
767 | 767 | observer_objects = just_observers + observer_objects |
|
768 | 768 | |
|
769 | 769 | try: |
|
770 | 770 | observers = validate_observers( |
|
771 | 771 | observer_objects, default_reviewers_data) |
|
772 | 772 | except ValueError as e: |
|
773 | 773 | raise JSONRPCError('Observer Validation: {}'.format(e)) |
|
774 | 774 | |
|
775 | 775 | title = Optional.extract(title) |
|
776 | 776 | if not title: |
|
777 | 777 | title_source_ref = source_ref_obj.name |
|
778 | 778 | title = PullRequestModel().generate_pullrequest_title( |
|
779 | 779 | source=source_repo, |
|
780 | 780 | source_ref=title_source_ref, |
|
781 | 781 | target=target_repo |
|
782 | 782 | ) |
|
783 | 783 | |
|
784 | 784 | diff_info = default_reviewers_data['diff_info'] |
|
785 | 785 | common_ancestor_id = diff_info['ancestor'] |
|
786 | 786 | # NOTE(marcink): reversed is consistent with how we open it in the WEB interface |
|
787 | 787 | commits = [commit['commit_id'] for commit in reversed(diff_info['commits'])] |
|
788 | 788 | |
|
789 | 789 | if not common_ancestor_id: |
|
790 | 790 | raise JSONRPCError('no common ancestor found between specified references') |
|
791 | 791 | |
|
792 | 792 | if not commits: |
|
793 | 793 | raise JSONRPCError('no commits found for merge between specified references') |
|
794 | 794 | |
|
795 | 795 | # recalculate target ref based on ancestor |
|
796 | 796 | full_target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, common_ancestor_id)) |
|
797 | 797 | |
|
798 | 798 | # fetch renderer, if set fallback to plain in case of PR |
|
799 | 799 | rc_config = SettingsModel().get_all_settings() |
|
800 | 800 | default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain') |
|
801 | 801 | description = Optional.extract(description) |
|
802 | 802 | description_renderer = Optional.extract(description_renderer) or default_system_renderer |
|
803 | 803 | |
|
804 | 804 | pull_request = PullRequestModel().create( |
|
805 | 805 | created_by=owner.user_id, |
|
806 | 806 | source_repo=source_repo, |
|
807 | 807 | source_ref=full_source_ref, |
|
808 | 808 | target_repo=target_repo, |
|
809 | 809 | target_ref=full_target_ref, |
|
810 | 810 | common_ancestor_id=common_ancestor_id, |
|
811 | 811 | revisions=commits, |
|
812 | 812 | reviewers=reviewers, |
|
813 | 813 | observers=observers, |
|
814 | 814 | title=title, |
|
815 | 815 | description=description, |
|
816 | 816 | description_renderer=description_renderer, |
|
817 | 817 | reviewer_data=default_reviewers_data, |
|
818 | 818 | auth_user=apiuser |
|
819 | 819 | ) |
|
820 | 820 | |
|
821 | 821 | Session().commit() |
|
822 | 822 | data = { |
|
823 | 823 | 'msg': 'Created new pull request `{}`'.format(title), |
|
824 | 824 | 'pull_request_id': pull_request.pull_request_id, |
|
825 | 825 | } |
|
826 | 826 | return data |
|
827 | 827 | |
|
828 | 828 | |
|
829 | 829 | @jsonrpc_method() |
|
830 | 830 | def update_pull_request( |
|
831 | 831 | request, apiuser, pullrequestid, repoid=Optional(None), |
|
832 | 832 | title=Optional(''), description=Optional(''), description_renderer=Optional(''), |
|
833 | 833 | reviewers=Optional(None), observers=Optional(None), update_commits=Optional(None)): |
|
834 | 834 | """ |
|
835 | 835 | Updates a pull request. |
|
836 | 836 | |
|
837 | 837 | :param apiuser: This is filled automatically from the |authtoken|. |
|
838 | 838 | :type apiuser: AuthUser |
|
839 | 839 | :param repoid: Optional repository name or repository ID. |
|
840 | 840 | :type repoid: str or int |
|
841 | 841 | :param pullrequestid: The pull request ID. |
|
842 | 842 | :type pullrequestid: int |
|
843 | 843 | :param title: Set the pull request title. |
|
844 | 844 | :type title: str |
|
845 | 845 | :param description: Update pull request description. |
|
846 | 846 | :type description: Optional(str) |
|
847 | 847 | :type description_renderer: Optional(str) |
|
848 | 848 | :param description_renderer: Update pull request renderer for the description. |
|
849 | 849 | It should be 'rst', 'markdown' or 'plain' |
|
850 | 850 | :param reviewers: Update pull request reviewers list with new value. |
|
851 | 851 | :type reviewers: Optional(list) |
|
852 | 852 | Accepts username strings or objects of the format: |
|
853 | 853 | |
|
854 | 854 | [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}] |
|
855 | 855 | :param observers: Update pull request observers list with new value. |
|
856 | 856 | :type observers: Optional(list) |
|
857 | 857 | Accepts username strings or objects of the format: |
|
858 | 858 | |
|
859 | 859 | [{'username': 'nick', 'reasons': ['should be aware about this PR']}] |
|
860 | 860 | :param update_commits: Trigger update of commits for this pull request |
|
861 | 861 | :type: update_commits: Optional(bool) |
|
862 | 862 | |
|
863 | 863 | Example output: |
|
864 | 864 | |
|
865 | 865 | .. code-block:: bash |
|
866 | 866 | |
|
867 | 867 | id : <id_given_in_input> |
|
868 | 868 | result : { |
|
869 | 869 | "msg": "Updated pull request `63`", |
|
870 | 870 | "pull_request": <pull_request_object>, |
|
871 | 871 | "updated_reviewers": { |
|
872 | 872 | "added": [ |
|
873 | 873 | "username" |
|
874 | 874 | ], |
|
875 | 875 | "removed": [] |
|
876 | 876 | }, |
|
877 | 877 | "updated_observers": { |
|
878 | 878 | "added": [ |
|
879 | 879 | "username" |
|
880 | 880 | ], |
|
881 | 881 | "removed": [] |
|
882 | 882 | }, |
|
883 | 883 | "updated_commits": { |
|
884 | 884 | "added": [ |
|
885 | 885 | "<sha1_hash>" |
|
886 | 886 | ], |
|
887 | 887 | "common": [ |
|
888 | 888 | "<sha1_hash>", |
|
889 | 889 | "<sha1_hash>", |
|
890 | 890 | ], |
|
891 | 891 | "removed": [] |
|
892 | 892 | } |
|
893 | 893 | } |
|
894 | 894 | error : null |
|
895 | 895 | """ |
|
896 | 896 | |
|
897 | 897 | pull_request = get_pull_request_or_error(pullrequestid) |
|
898 | 898 | if Optional.extract(repoid): |
|
899 | 899 | repo = get_repo_or_error(repoid) |
|
900 | 900 | else: |
|
901 | 901 | repo = pull_request.target_repo |
|
902 | 902 | |
|
903 | 903 | if not PullRequestModel().check_user_update( |
|
904 | 904 | pull_request, apiuser, api=True): |
|
905 | 905 | raise JSONRPCError( |
|
906 | 906 | 'pull request `%s` update failed, no permission to update.' % ( |
|
907 | 907 | pullrequestid,)) |
|
908 | 908 | if pull_request.is_closed(): |
|
909 | 909 | raise JSONRPCError( |
|
910 | 910 | 'pull request `%s` update failed, pull request is closed' % ( |
|
911 | 911 | pullrequestid,)) |
|
912 | 912 | |
|
913 | 913 | reviewer_objects = Optional.extract(reviewers) or [] |
|
914 | 914 | observer_objects = Optional.extract(observers) or [] |
|
915 | 915 | |
|
916 | 916 | title = Optional.extract(title) |
|
917 | 917 | description = Optional.extract(description) |
|
918 | 918 | description_renderer = Optional.extract(description_renderer) |
|
919 | 919 | |
|
920 | 920 | # Update title/description |
|
921 | 921 | title_changed = False |
|
922 | 922 | if title or description: |
|
923 | 923 | PullRequestModel().edit( |
|
924 | 924 | pull_request, |
|
925 | 925 | title or pull_request.title, |
|
926 | 926 | description or pull_request.description, |
|
927 | 927 | description_renderer or pull_request.description_renderer, |
|
928 | 928 | apiuser) |
|
929 | 929 | Session().commit() |
|
930 | 930 | title_changed = True |
|
931 | 931 | |
|
932 | 932 | commit_changes = {"added": [], "common": [], "removed": []} |
|
933 | 933 | |
|
934 | 934 | # Update commits |
|
935 | 935 | commits_changed = False |
|
936 | 936 | if str2bool(Optional.extract(update_commits)): |
|
937 | 937 | |
|
938 | 938 | if pull_request.pull_request_state != PullRequest.STATE_CREATED: |
|
939 | 939 | raise JSONRPCError( |
|
940 | 940 | 'Operation forbidden because pull request is in state {}, ' |
|
941 | 941 | 'only state {} is allowed.'.format( |
|
942 | 942 | pull_request.pull_request_state, PullRequest.STATE_CREATED)) |
|
943 | 943 | |
|
944 | 944 | with pull_request.set_state(PullRequest.STATE_UPDATING): |
|
945 | 945 | if PullRequestModel().has_valid_update_type(pull_request): |
|
946 | 946 | db_user = apiuser.get_instance() |
|
947 | 947 | update_response = PullRequestModel().update_commits( |
|
948 | 948 | pull_request, db_user) |
|
949 | 949 | commit_changes = update_response.changes or commit_changes |
|
950 | 950 | Session().commit() |
|
951 | 951 | commits_changed = True |
|
952 | 952 | |
|
953 | 953 | # Update reviewers |
|
954 | 954 | # serialize and validate passed in given reviewers |
|
955 | 955 | if reviewer_objects: |
|
956 | 956 | reviewer_objects = _reviewers_validation(reviewer_objects) |
|
957 | 957 | |
|
958 | 958 | if observer_objects: |
|
959 | 959 | observer_objects = _reviewers_validation(reviewer_objects) |
|
960 | 960 | |
|
961 | 961 | # re-use stored rules |
|
962 | 962 | default_reviewers_data = pull_request.reviewer_data |
|
963 | 963 | |
|
964 | 964 | __, validate_default_reviewers, validate_observers = \ |
|
965 | 965 | PullRequestModel().get_reviewer_functions() |
|
966 | 966 | |
|
967 | 967 | if reviewer_objects: |
|
968 | 968 | try: |
|
969 | 969 | reviewers = validate_default_reviewers(reviewer_objects, default_reviewers_data) |
|
970 | 970 | except ValueError as e: |
|
971 | 971 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) |
|
972 | 972 | else: |
|
973 | 973 | reviewers = [] |
|
974 | 974 | |
|
975 | 975 | if observer_objects: |
|
976 | 976 | try: |
|
977 | 977 | observers = validate_default_reviewers(reviewer_objects, default_reviewers_data) |
|
978 | 978 | except ValueError as e: |
|
979 | 979 | raise JSONRPCError('Observer Validation: {}'.format(e)) |
|
980 | 980 | else: |
|
981 | 981 | observers = [] |
|
982 | 982 | |
|
983 | 983 | reviewers_changed = False |
|
984 | 984 | reviewers_changes = {"added": [], "removed": []} |
|
985 | 985 | if reviewers: |
|
986 | 986 | old_calculated_status = pull_request.calculated_review_status() |
|
987 | 987 | added_reviewers, removed_reviewers = \ |
|
988 | 988 | PullRequestModel().update_reviewers(pull_request, reviewers, apiuser.get_instance()) |
|
989 | 989 | |
|
990 | 990 | reviewers_changes['added'] = sorted( |
|
991 | 991 | [get_user_or_error(n).username for n in added_reviewers]) |
|
992 | 992 | reviewers_changes['removed'] = sorted( |
|
993 | 993 | [get_user_or_error(n).username for n in removed_reviewers]) |
|
994 | 994 | Session().commit() |
|
995 | 995 | |
|
996 | 996 | # trigger status changed if change in reviewers changes the status |
|
997 | 997 | calculated_status = pull_request.calculated_review_status() |
|
998 | 998 | if old_calculated_status != calculated_status: |
|
999 | 999 | PullRequestModel().trigger_pull_request_hook( |
|
1000 | 1000 | pull_request, apiuser, 'review_status_change', |
|
1001 | 1001 | data={'status': calculated_status}) |
|
1002 | 1002 | reviewers_changed = True |
|
1003 | 1003 | |
|
1004 | 1004 | observers_changed = False |
|
1005 | 1005 | observers_changes = {"added": [], "removed": []} |
|
1006 | 1006 | if observers: |
|
1007 | 1007 | added_observers, removed_observers = \ |
|
1008 | 1008 | PullRequestModel().update_observers(pull_request, observers, apiuser.get_instance()) |
|
1009 | 1009 | |
|
1010 | 1010 | observers_changes['added'] = sorted( |
|
1011 | 1011 | [get_user_or_error(n).username for n in added_observers]) |
|
1012 | 1012 | observers_changes['removed'] = sorted( |
|
1013 | 1013 | [get_user_or_error(n).username for n in removed_observers]) |
|
1014 | 1014 | Session().commit() |
|
1015 | 1015 | |
|
1016 | 1016 | reviewers_changed = True |
|
1017 | 1017 | |
|
1018 | 1018 | # push changed to channelstream |
|
1019 | 1019 | if commits_changed or reviewers_changed or observers_changed: |
|
1020 | 1020 | pr_broadcast_channel = channelstream.pr_channel(pull_request) |
|
1021 | 1021 | msg = 'Pull request was updated.' |
|
1022 | 1022 | channelstream.pr_update_channelstream_push( |
|
1023 | 1023 | request, pr_broadcast_channel, apiuser, msg) |
|
1024 | 1024 | |
|
1025 | 1025 | data = { |
|
1026 | 1026 | 'msg': 'Updated pull request `{}`'.format(pull_request.pull_request_id), |
|
1027 | 1027 | 'pull_request': pull_request.get_api_data(), |
|
1028 | 1028 | 'updated_commits': commit_changes, |
|
1029 | 1029 | 'updated_reviewers': reviewers_changes, |
|
1030 | 1030 | 'updated_observers': observers_changes, |
|
1031 | 1031 | } |
|
1032 | 1032 | |
|
1033 | 1033 | return data |
|
1034 | 1034 | |
|
1035 | 1035 | |
|
1036 | 1036 | @jsonrpc_method() |
|
1037 | 1037 | def close_pull_request( |
|
1038 | 1038 | request, apiuser, pullrequestid, repoid=Optional(None), |
|
1039 | 1039 | userid=Optional(OAttr('apiuser')), message=Optional('')): |
|
1040 | 1040 | """ |
|
1041 | 1041 | Close the pull request specified by `pullrequestid`. |
|
1042 | 1042 | |
|
1043 | 1043 | :param apiuser: This is filled automatically from the |authtoken|. |
|
1044 | 1044 | :type apiuser: AuthUser |
|
1045 | 1045 | :param repoid: Repository name or repository ID to which the pull |
|
1046 | 1046 | request belongs. |
|
1047 | 1047 | :type repoid: str or int |
|
1048 | 1048 | :param pullrequestid: ID of the pull request to be closed. |
|
1049 | 1049 | :type pullrequestid: int |
|
1050 | 1050 | :param userid: Close the pull request as this user. |
|
1051 | 1051 | :type userid: Optional(str or int) |
|
1052 | 1052 | :param message: Optional message to close the Pull Request with. If not |
|
1053 | 1053 | specified it will be generated automatically. |
|
1054 | 1054 | :type message: Optional(str) |
|
1055 | 1055 | |
|
1056 | 1056 | Example output: |
|
1057 | 1057 | |
|
1058 | 1058 | .. code-block:: bash |
|
1059 | 1059 | |
|
1060 | 1060 | "id": <id_given_in_input>, |
|
1061 | 1061 | "result": { |
|
1062 | 1062 | "pull_request_id": "<int>", |
|
1063 | 1063 | "close_status": "<str:status_lbl>, |
|
1064 | 1064 | "closed": "<bool>" |
|
1065 | 1065 | }, |
|
1066 | 1066 | "error": null |
|
1067 | 1067 | |
|
1068 | 1068 | """ |
|
1069 | 1069 | _ = request.translate |
|
1070 | 1070 | |
|
1071 | 1071 | pull_request = get_pull_request_or_error(pullrequestid) |
|
1072 | 1072 | if Optional.extract(repoid): |
|
1073 | 1073 | repo = get_repo_or_error(repoid) |
|
1074 | 1074 | else: |
|
1075 | 1075 | repo = pull_request.target_repo |
|
1076 | 1076 | |
|
1077 | 1077 | is_repo_admin = HasRepoPermissionAnyApi('repository.admin')( |
|
1078 | 1078 | user=apiuser, repo_name=repo.repo_name) |
|
1079 | 1079 | if not isinstance(userid, Optional): |
|
1080 | 1080 | if has_superadmin_permission(apiuser) or is_repo_admin: |
|
1081 | 1081 | apiuser = get_user_or_error(userid) |
|
1082 | 1082 | else: |
|
1083 | 1083 | raise JSONRPCError('userid is not the same as your user') |
|
1084 | 1084 | |
|
1085 | 1085 | if pull_request.is_closed(): |
|
1086 | 1086 | raise JSONRPCError( |
|
1087 | 1087 | 'pull request `%s` is already closed' % (pullrequestid,)) |
|
1088 | 1088 | |
|
1089 | 1089 | # only owner or admin or person with write permissions |
|
1090 | 1090 | allowed_to_close = PullRequestModel().check_user_update( |
|
1091 | 1091 | pull_request, apiuser, api=True) |
|
1092 | 1092 | |
|
1093 | 1093 | if not allowed_to_close: |
|
1094 | 1094 | raise JSONRPCError( |
|
1095 | 1095 | 'pull request `%s` close failed, no permission to close.' % ( |
|
1096 | 1096 | pullrequestid,)) |
|
1097 | 1097 | |
|
1098 | 1098 | # message we're using to close the PR, else it's automatically generated |
|
1099 | 1099 | message = Optional.extract(message) |
|
1100 | 1100 | |
|
1101 | 1101 | # finally close the PR, with proper message comment |
|
1102 | 1102 | comment, status = PullRequestModel().close_pull_request_with_comment( |
|
1103 | 1103 | pull_request, apiuser, repo, message=message, auth_user=apiuser) |
|
1104 | 1104 | status_lbl = ChangesetStatus.get_status_lbl(status) |
|
1105 | 1105 | |
|
1106 | 1106 | Session().commit() |
|
1107 | 1107 | |
|
1108 | 1108 | data = { |
|
1109 | 1109 | 'pull_request_id': pull_request.pull_request_id, |
|
1110 | 1110 | 'close_status': status_lbl, |
|
1111 | 1111 | 'closed': True, |
|
1112 | 1112 | } |
|
1113 | 1113 | return data |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
|
1 | NO CONTENT: modified file | |
The requested commit or file is too big and content was truncated. Show full diff |
General Comments 0
You need to be logged in to leave comments.
Login now