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

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

@@ -1,58 +1,58 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest # noqa
20 import pytest # noqa
21
21
22 # keep the imports to have a toplevel conftest.py but still importable from EE edition
22 # keep the imports to have a toplevel conftest.py but still importable from EE edition
23 from rhodecode.tests.conftest_common import ( # noqa
23 from rhodecode.tests.conftest_common import ( # noqa
24 pytest_generate_tests,
24 pytest_generate_tests,
25 pytest_runtest_makereport,
25 pytest_runtest_makereport,
26 pytest_addoption
26 pytest_addoption
27 )
27 )
28
28
29
29
30 pytest_plugins = [
30 pytest_plugins = [
31 "rhodecode.tests.fixture_mods.fixture_pyramid",
31 "rhodecode.tests.fixture_mods.fixture_pyramid",
32 "rhodecode.tests.fixture_mods.fixture_utils",
32 "rhodecode.tests.fixture_mods.fixture_utils",
33 ]
33 ]
34
34
35
35
36 def pytest_configure(config):
36 def pytest_configure(config):
37 from rhodecode.config import patches # noqa
37 from rhodecode.config import patches # noqa
38
38
39
39
40 def pytest_collection_modifyitems(session, config, items):
40 def pytest_collection_modifyitems(session, config, items):
41 # nottest marked, compare nose, used for transition from nose to pytest
41 # nottest marked, compare nose, used for transition from nose to pytest
42 remaining = [
42 remaining = [
43 i for i in items if getattr(i.obj, '__test__', True)]
43 i for i in items if getattr(i.obj, '__test__', True)]
44 items[:] = remaining
44 items[:] = remaining
45
45
46 # NOTE(marcink): custom test ordering, db tests and vcstests are slowest and should
46 # NOTE(marcink): custom test ordering, db tests and vcstests are slowest and should
47 # be executed at the end for faster test feedback
47 # be executed at the end for faster test feedback
48 def sorter(item):
48 def sorter(item):
49 pos = 0
49 pos = 0
50 key = item._nodeid
50 key = item._nodeid
51 if key.startswith('rhodecode/tests/database'):
51 if key.startswith('rhodecode/tests/database'):
52 pos = 1
52 pos = 1
53 elif key.startswith('rhodecode/tests/vcs_operations'):
53 elif key.startswith('rhodecode/tests/vcs_operations'):
54 pos = 2
54 pos = 2
55
55
56 return pos
56 return pos
57
57
58 items.sort(key=sorter)
58 items.sort(key=sorter)
@@ -1,88 +1,88 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import os
20 import os
21 import datetime
21 import datetime
22 import collections
22 import collections
23
23
24 now = datetime.datetime.now()
24 now = datetime.datetime.now()
25 now = now.strftime("%Y-%m-%d %H:%M:%S") + '.' + "{:03d}".format(int(now.microsecond/1000))
25 now = now.strftime("%Y-%m-%d %H:%M:%S") + '.' + "{:03d}".format(int(now.microsecond/1000))
26
26
27 print(f'{now} Starting RhodeCode imports...')
27 print(f'{now} Starting RhodeCode imports...')
28
28
29 VERSION = tuple(open(os.path.join(
29 VERSION = tuple(open(os.path.join(
30 os.path.dirname(__file__), 'VERSION')).read().split('.'))
30 os.path.dirname(__file__), 'VERSION')).read().split('.'))
31
31
32 BACKENDS = collections.OrderedDict()
32 BACKENDS = collections.OrderedDict()
33
33
34 BACKENDS['hg'] = 'Mercurial repository'
34 BACKENDS['hg'] = 'Mercurial repository'
35 BACKENDS['git'] = 'Git repository'
35 BACKENDS['git'] = 'Git repository'
36 BACKENDS['svn'] = 'Subversion repository'
36 BACKENDS['svn'] = 'Subversion repository'
37
37
38
38
39 CELERY_ENABLED = False
39 CELERY_ENABLED = False
40 CELERY_EAGER = False
40 CELERY_EAGER = False
41
41
42 # link to config for pyramid
42 # link to config for pyramid
43 CONFIG = {}
43 CONFIG = {}
44
44
45
45
46 class ConfigGet:
46 class ConfigGet:
47 NotGiven = object()
47 NotGiven = object()
48
48
49 def _get_val_or_missing(self, key, missing):
49 def _get_val_or_missing(self, key, missing):
50 if key not in CONFIG:
50 if key not in CONFIG:
51 if missing == self.NotGiven:
51 if missing == self.NotGiven:
52 return missing
52 return missing
53 # we don't get key, we don't get missing value, return nothing similar as config.get(key)
53 # we don't get key, we don't get missing value, return nothing similar as config.get(key)
54 return None
54 return None
55 else:
55 else:
56 val = CONFIG[key]
56 val = CONFIG[key]
57 return val
57 return val
58
58
59 def get_str(self, key, missing=NotGiven):
59 def get_str(self, key, missing=NotGiven):
60 from rhodecode.lib.str_utils import safe_str
60 from rhodecode.lib.str_utils import safe_str
61 val = self._get_val_or_missing(key, missing)
61 val = self._get_val_or_missing(key, missing)
62 return safe_str(val)
62 return safe_str(val)
63
63
64 def get_int(self, key, missing=NotGiven):
64 def get_int(self, key, missing=NotGiven):
65 from rhodecode.lib.str_utils import safe_int
65 from rhodecode.lib.str_utils import safe_int
66 val = self._get_val_or_missing(key, missing)
66 val = self._get_val_or_missing(key, missing)
67 return safe_int(val)
67 return safe_int(val)
68
68
69 def get_bool(self, key, missing=NotGiven):
69 def get_bool(self, key, missing=NotGiven):
70 from rhodecode.lib.type_utils import str2bool
70 from rhodecode.lib.type_utils import str2bool
71 val = self._get_val_or_missing(key, missing)
71 val = self._get_val_or_missing(key, missing)
72 return str2bool(val)
72 return str2bool(val)
73
73
74 # Populated with the settings dictionary from application init in
74 # Populated with the settings dictionary from application init in
75 # rhodecode.conf.environment.load_pyramid_environment
75 # rhodecode.conf.environment.load_pyramid_environment
76 PYRAMID_SETTINGS = {}
76 PYRAMID_SETTINGS = {}
77
77
78 # Linked module for extensions
78 # Linked module for extensions
79 EXTENSIONS = {}
79 EXTENSIONS = {}
80
80
81 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
81 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
82 __dbversion__ = 114 # defines current db version for migrations
82 __dbversion__ = 114 # defines current db version for migrations
83 __license__ = 'AGPLv3, and Commercial License'
83 __license__ = 'AGPLv3, and Commercial License'
84 __author__ = 'RhodeCode GmbH'
84 __author__ = 'RhodeCode GmbH'
85 __url__ = 'https://code.rhodecode.com'
85 __url__ = 'https://code.rhodecode.com'
86
86
87 is_test = False
87 is_test = False
88 disable_error_handler = False
88 disable_error_handler = False
@@ -1,576 +1,576 b''
1
1
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import itertools
21 import itertools
22 import logging
22 import logging
23 import sys
23 import sys
24 import fnmatch
24 import fnmatch
25
25
26 import decorator
26 import decorator
27 import typing
27 import typing
28 import venusian
28 import venusian
29 from collections import OrderedDict
29 from collections import OrderedDict
30
30
31 from pyramid.exceptions import ConfigurationError
31 from pyramid.exceptions import ConfigurationError
32 from pyramid.renderers import render
32 from pyramid.renderers import render
33 from pyramid.response import Response
33 from pyramid.response import Response
34 from pyramid.httpexceptions import HTTPNotFound
34 from pyramid.httpexceptions import HTTPNotFound
35
35
36 from rhodecode.api.exc import (
36 from rhodecode.api.exc import (
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
37 JSONRPCBaseError, JSONRPCError, JSONRPCForbidden, JSONRPCValidationError)
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.apps._base import TemplateArgs
39 from rhodecode.lib.auth import AuthUser
39 from rhodecode.lib.auth import AuthUser
40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
40 from rhodecode.lib.base import get_ip_addr, attach_context_attributes
41 from rhodecode.lib.exc_tracking import store_exception
41 from rhodecode.lib.exc_tracking import store_exception
42 from rhodecode.lib import ext_json
42 from rhodecode.lib import ext_json
43 from rhodecode.lib.utils2 import safe_str
43 from rhodecode.lib.utils2 import safe_str
44 from rhodecode.lib.plugins.utils import get_plugin_settings
44 from rhodecode.lib.plugins.utils import get_plugin_settings
45 from rhodecode.model.db import User, UserApiKeys
45 from rhodecode.model.db import User, UserApiKeys
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49 DEFAULT_RENDERER = 'jsonrpc_renderer'
49 DEFAULT_RENDERER = 'jsonrpc_renderer'
50 DEFAULT_URL = '/_admin/apiv2'
50 DEFAULT_URL = '/_admin/apiv2'
51
51
52
52
53 def find_methods(jsonrpc_methods, pattern):
53 def find_methods(jsonrpc_methods, pattern):
54 matches = OrderedDict()
54 matches = OrderedDict()
55 if not isinstance(pattern, (list, tuple)):
55 if not isinstance(pattern, (list, tuple)):
56 pattern = [pattern]
56 pattern = [pattern]
57
57
58 for single_pattern in pattern:
58 for single_pattern in pattern:
59 for method_name, method in jsonrpc_methods.items():
59 for method_name, method in jsonrpc_methods.items():
60 if fnmatch.fnmatch(method_name, single_pattern):
60 if fnmatch.fnmatch(method_name, single_pattern):
61 matches[method_name] = method
61 matches[method_name] = method
62 return matches
62 return matches
63
63
64
64
65 class ExtJsonRenderer(object):
65 class ExtJsonRenderer(object):
66 """
66 """
67 Custom renderer that makes use of our ext_json lib
67 Custom renderer that makes use of our ext_json lib
68
68
69 """
69 """
70
70
71 def __init__(self):
71 def __init__(self):
72 self.serializer = ext_json.formatted_json
72 self.serializer = ext_json.formatted_json
73
73
74 def __call__(self, info):
74 def __call__(self, info):
75 """ Returns a plain JSON-encoded string with content-type
75 """ Returns a plain JSON-encoded string with content-type
76 ``application/json``. The content-type may be overridden by
76 ``application/json``. The content-type may be overridden by
77 setting ``request.response.content_type``."""
77 setting ``request.response.content_type``."""
78
78
79 def _render(value, system):
79 def _render(value, system):
80 request = system.get('request')
80 request = system.get('request')
81 if request is not None:
81 if request is not None:
82 response = request.response
82 response = request.response
83 ct = response.content_type
83 ct = response.content_type
84 if ct == response.default_content_type:
84 if ct == response.default_content_type:
85 response.content_type = 'application/json'
85 response.content_type = 'application/json'
86
86
87 return self.serializer(value)
87 return self.serializer(value)
88
88
89 return _render
89 return _render
90
90
91
91
92 def jsonrpc_response(request, result):
92 def jsonrpc_response(request, result):
93 rpc_id = getattr(request, 'rpc_id', None)
93 rpc_id = getattr(request, 'rpc_id', None)
94
94
95 ret_value = ''
95 ret_value = ''
96 if rpc_id:
96 if rpc_id:
97 ret_value = {'id': rpc_id, 'result': result, 'error': None}
97 ret_value = {'id': rpc_id, 'result': result, 'error': None}
98
98
99 # fetch deprecation warnings, and store it inside results
99 # fetch deprecation warnings, and store it inside results
100 deprecation = getattr(request, 'rpc_deprecation', None)
100 deprecation = getattr(request, 'rpc_deprecation', None)
101 if deprecation:
101 if deprecation:
102 ret_value['DEPRECATION_WARNING'] = deprecation
102 ret_value['DEPRECATION_WARNING'] = deprecation
103
103
104 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
104 raw_body = render(DEFAULT_RENDERER, ret_value, request=request)
105 content_type = 'application/json'
105 content_type = 'application/json'
106 content_type_header = 'Content-Type'
106 content_type_header = 'Content-Type'
107 headers = {
107 headers = {
108 content_type_header: content_type
108 content_type_header: content_type
109 }
109 }
110 return Response(
110 return Response(
111 body=raw_body,
111 body=raw_body,
112 content_type=content_type,
112 content_type=content_type,
113 headerlist=[(k, v) for k, v in headers.items()]
113 headerlist=[(k, v) for k, v in headers.items()]
114 )
114 )
115
115
116
116
117 def jsonrpc_error(request, message, retid=None, code: typing.Optional[int] = None, headers: typing.Optional[dict] = None):
117 def jsonrpc_error(request, message, retid=None, code: typing.Optional[int] = None, headers: typing.Optional[dict] = None):
118 """
118 """
119 Generate a Response object with a JSON-RPC error body
119 Generate a Response object with a JSON-RPC error body
120 """
120 """
121 headers = headers or {}
121 headers = headers or {}
122 content_type = 'application/json'
122 content_type = 'application/json'
123 content_type_header = 'Content-Type'
123 content_type_header = 'Content-Type'
124 if content_type_header not in headers:
124 if content_type_header not in headers:
125 headers[content_type_header] = content_type
125 headers[content_type_header] = content_type
126
126
127 err_dict = {'id': retid, 'result': None, 'error': message}
127 err_dict = {'id': retid, 'result': None, 'error': message}
128 raw_body = render(DEFAULT_RENDERER, err_dict, request=request)
128 raw_body = render(DEFAULT_RENDERER, err_dict, request=request)
129
129
130 return Response(
130 return Response(
131 body=raw_body,
131 body=raw_body,
132 status=code,
132 status=code,
133 content_type=content_type,
133 content_type=content_type,
134 headerlist=[(k, v) for k, v in headers.items()]
134 headerlist=[(k, v) for k, v in headers.items()]
135 )
135 )
136
136
137
137
138 def exception_view(exc, request):
138 def exception_view(exc, request):
139 rpc_id = getattr(request, 'rpc_id', None)
139 rpc_id = getattr(request, 'rpc_id', None)
140
140
141 if isinstance(exc, JSONRPCError):
141 if isinstance(exc, JSONRPCError):
142 fault_message = safe_str(exc.message)
142 fault_message = safe_str(exc.message)
143 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
143 log.debug('json-rpc error rpc_id:%s "%s"', rpc_id, fault_message)
144 elif isinstance(exc, JSONRPCValidationError):
144 elif isinstance(exc, JSONRPCValidationError):
145 colander_exc = exc.colander_exception
145 colander_exc = exc.colander_exception
146 # TODO(marcink): think maybe of nicer way to serialize errors ?
146 # TODO(marcink): think maybe of nicer way to serialize errors ?
147 fault_message = colander_exc.asdict()
147 fault_message = colander_exc.asdict()
148 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
148 log.debug('json-rpc colander error rpc_id:%s "%s"', rpc_id, fault_message)
149 elif isinstance(exc, JSONRPCForbidden):
149 elif isinstance(exc, JSONRPCForbidden):
150 fault_message = 'Access was denied to this resource.'
150 fault_message = 'Access was denied to this resource.'
151 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
151 log.warning('json-rpc forbidden call rpc_id:%s "%s"', rpc_id, fault_message)
152 elif isinstance(exc, HTTPNotFound):
152 elif isinstance(exc, HTTPNotFound):
153 method = request.rpc_method
153 method = request.rpc_method
154 log.debug('json-rpc method `%s` not found in list of '
154 log.debug('json-rpc method `%s` not found in list of '
155 'api calls: %s, rpc_id:%s',
155 'api calls: %s, rpc_id:%s',
156 method, list(request.registry.jsonrpc_methods.keys()), rpc_id)
156 method, list(request.registry.jsonrpc_methods.keys()), rpc_id)
157
157
158 similar = 'none'
158 similar = 'none'
159 try:
159 try:
160 similar_paterns = [f'*{x}*' for x in method.split('_')]
160 similar_paterns = [f'*{x}*' for x in method.split('_')]
161 similar_found = find_methods(
161 similar_found = find_methods(
162 request.registry.jsonrpc_methods, similar_paterns)
162 request.registry.jsonrpc_methods, similar_paterns)
163 similar = ', '.join(similar_found.keys()) or similar
163 similar = ', '.join(similar_found.keys()) or similar
164 except Exception:
164 except Exception:
165 # make the whole above block safe
165 # make the whole above block safe
166 pass
166 pass
167
167
168 fault_message = "No such method: {}. Similar methods: {}".format(
168 fault_message = "No such method: {}. Similar methods: {}".format(
169 method, similar)
169 method, similar)
170 else:
170 else:
171 fault_message = 'undefined error'
171 fault_message = 'undefined error'
172 exc_info = exc.exc_info()
172 exc_info = exc.exc_info()
173 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
173 store_exception(id(exc_info), exc_info, prefix='rhodecode-api')
174
174
175 statsd = request.registry.statsd
175 statsd = request.registry.statsd
176 if statsd:
176 if statsd:
177 exc_type = "{}.{}".format(exc.__class__.__module__, exc.__class__.__name__)
177 exc_type = "{}.{}".format(exc.__class__.__module__, exc.__class__.__name__)
178 statsd.incr('rhodecode_exception_total',
178 statsd.incr('rhodecode_exception_total',
179 tags=["exc_source:api", "type:{}".format(exc_type)])
179 tags=["exc_source:api", "type:{}".format(exc_type)])
180
180
181 return jsonrpc_error(request, fault_message, rpc_id)
181 return jsonrpc_error(request, fault_message, rpc_id)
182
182
183
183
184 def request_view(request):
184 def request_view(request):
185 """
185 """
186 Main request handling method. It handles all logic to call a specific
186 Main request handling method. It handles all logic to call a specific
187 exposed method
187 exposed method
188 """
188 """
189 # cython compatible inspect
189 # cython compatible inspect
190 from rhodecode.config.patches import inspect_getargspec
190 from rhodecode.config.patches import inspect_getargspec
191 inspect = inspect_getargspec()
191 inspect = inspect_getargspec()
192
192
193 # check if we can find this session using api_key, get_by_auth_token
193 # check if we can find this session using api_key, get_by_auth_token
194 # search not expired tokens only
194 # search not expired tokens only
195 try:
195 try:
196 api_user = User.get_by_auth_token(request.rpc_api_key)
196 api_user = User.get_by_auth_token(request.rpc_api_key)
197
197
198 if api_user is None:
198 if api_user is None:
199 return jsonrpc_error(
199 return jsonrpc_error(
200 request, retid=request.rpc_id, message='Invalid API KEY')
200 request, retid=request.rpc_id, message='Invalid API KEY')
201
201
202 if not api_user.active:
202 if not api_user.active:
203 return jsonrpc_error(
203 return jsonrpc_error(
204 request, retid=request.rpc_id,
204 request, retid=request.rpc_id,
205 message='Request from this user not allowed')
205 message='Request from this user not allowed')
206
206
207 # check if we are allowed to use this IP
207 # check if we are allowed to use this IP
208 auth_u = AuthUser(
208 auth_u = AuthUser(
209 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
209 api_user.user_id, request.rpc_api_key, ip_addr=request.rpc_ip_addr)
210 if not auth_u.ip_allowed:
210 if not auth_u.ip_allowed:
211 return jsonrpc_error(
211 return jsonrpc_error(
212 request, retid=request.rpc_id,
212 request, retid=request.rpc_id,
213 message='Request from IP:%s not allowed' % (
213 message='Request from IP:%s not allowed' % (
214 request.rpc_ip_addr,))
214 request.rpc_ip_addr,))
215 else:
215 else:
216 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
216 log.info('Access for IP:%s allowed', request.rpc_ip_addr)
217
217
218 # register our auth-user
218 # register our auth-user
219 request.rpc_user = auth_u
219 request.rpc_user = auth_u
220 request.environ['rc_auth_user_id'] = str(auth_u.user_id)
220 request.environ['rc_auth_user_id'] = str(auth_u.user_id)
221
221
222 # now check if token is valid for API
222 # now check if token is valid for API
223 auth_token = request.rpc_api_key
223 auth_token = request.rpc_api_key
224 token_match = api_user.authenticate_by_token(
224 token_match = api_user.authenticate_by_token(
225 auth_token, roles=[UserApiKeys.ROLE_API])
225 auth_token, roles=[UserApiKeys.ROLE_API])
226 invalid_token = not token_match
226 invalid_token = not token_match
227
227
228 log.debug('Checking if API KEY is valid with proper role')
228 log.debug('Checking if API KEY is valid with proper role')
229 if invalid_token:
229 if invalid_token:
230 return jsonrpc_error(
230 return jsonrpc_error(
231 request, retid=request.rpc_id,
231 request, retid=request.rpc_id,
232 message='API KEY invalid or, has bad role for an API call')
232 message='API KEY invalid or, has bad role for an API call')
233
233
234 except Exception:
234 except Exception:
235 log.exception('Error on API AUTH')
235 log.exception('Error on API AUTH')
236 return jsonrpc_error(
236 return jsonrpc_error(
237 request, retid=request.rpc_id, message='Invalid API KEY')
237 request, retid=request.rpc_id, message='Invalid API KEY')
238
238
239 method = request.rpc_method
239 method = request.rpc_method
240 func = request.registry.jsonrpc_methods[method]
240 func = request.registry.jsonrpc_methods[method]
241
241
242 # now that we have a method, add request._req_params to
242 # now that we have a method, add request._req_params to
243 # self.kargs and dispatch control to WGIController
243 # self.kargs and dispatch control to WGIController
244
244
245 argspec = inspect.getargspec(func)
245 argspec = inspect.getargspec(func)
246 arglist = argspec[0]
246 arglist = argspec[0]
247 defs = argspec[3] or []
247 defs = argspec[3] or []
248 defaults = [type(a) for a in defs]
248 defaults = [type(a) for a in defs]
249 default_empty = type(NotImplemented)
249 default_empty = type(NotImplemented)
250
250
251 # kw arguments required by this method
251 # kw arguments required by this method
252 func_kwargs = dict(itertools.zip_longest(
252 func_kwargs = dict(itertools.zip_longest(
253 reversed(arglist), reversed(defaults), fillvalue=default_empty))
253 reversed(arglist), reversed(defaults), fillvalue=default_empty))
254
254
255 # This attribute will need to be first param of a method that uses
255 # This attribute will need to be first param of a method that uses
256 # api_key, which is translated to instance of user at that name
256 # api_key, which is translated to instance of user at that name
257 user_var = 'apiuser'
257 user_var = 'apiuser'
258 request_var = 'request'
258 request_var = 'request'
259
259
260 for arg in [user_var, request_var]:
260 for arg in [user_var, request_var]:
261 if arg not in arglist:
261 if arg not in arglist:
262 return jsonrpc_error(
262 return jsonrpc_error(
263 request,
263 request,
264 retid=request.rpc_id,
264 retid=request.rpc_id,
265 message='This method [%s] does not support '
265 message='This method [%s] does not support '
266 'required parameter `%s`' % (func.__name__, arg))
266 'required parameter `%s`' % (func.__name__, arg))
267
267
268 # get our arglist and check if we provided them as args
268 # get our arglist and check if we provided them as args
269 for arg, default in func_kwargs.items():
269 for arg, default in func_kwargs.items():
270 if arg in [user_var, request_var]:
270 if arg in [user_var, request_var]:
271 # user_var and request_var are pre-hardcoded parameters and we
271 # user_var and request_var are pre-hardcoded parameters and we
272 # don't need to do any translation
272 # don't need to do any translation
273 continue
273 continue
274
274
275 # skip the required param check if it's default value is
275 # skip the required param check if it's default value is
276 # NotImplementedType (default_empty)
276 # NotImplementedType (default_empty)
277 if default == default_empty and arg not in request.rpc_params:
277 if default == default_empty and arg not in request.rpc_params:
278 return jsonrpc_error(
278 return jsonrpc_error(
279 request,
279 request,
280 retid=request.rpc_id,
280 retid=request.rpc_id,
281 message=('Missing non optional `%s` arg in JSON DATA' % arg)
281 message=('Missing non optional `%s` arg in JSON DATA' % arg)
282 )
282 )
283
283
284 # sanitize extra passed arguments
284 # sanitize extra passed arguments
285 for k in list(request.rpc_params.keys()):
285 for k in list(request.rpc_params.keys()):
286 if k not in func_kwargs:
286 if k not in func_kwargs:
287 del request.rpc_params[k]
287 del request.rpc_params[k]
288
288
289 call_params = request.rpc_params
289 call_params = request.rpc_params
290 call_params.update({
290 call_params.update({
291 'request': request,
291 'request': request,
292 'apiuser': auth_u
292 'apiuser': auth_u
293 })
293 })
294
294
295 # register some common functions for usage
295 # register some common functions for usage
296 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
296 attach_context_attributes(TemplateArgs(), request, request.rpc_user.user_id)
297
297
298 statsd = request.registry.statsd
298 statsd = request.registry.statsd
299
299
300 try:
300 try:
301 ret_value = func(**call_params)
301 ret_value = func(**call_params)
302 resp = jsonrpc_response(request, ret_value)
302 resp = jsonrpc_response(request, ret_value)
303 if statsd:
303 if statsd:
304 statsd.incr('rhodecode_api_call_success_total')
304 statsd.incr('rhodecode_api_call_success_total')
305 return resp
305 return resp
306 except JSONRPCBaseError:
306 except JSONRPCBaseError:
307 raise
307 raise
308 except Exception:
308 except Exception:
309 log.exception('Unhandled exception occurred on api call: %s', func)
309 log.exception('Unhandled exception occurred on api call: %s', func)
310 exc_info = sys.exc_info()
310 exc_info = sys.exc_info()
311 exc_id, exc_type_name = store_exception(
311 exc_id, exc_type_name = store_exception(
312 id(exc_info), exc_info, prefix='rhodecode-api')
312 id(exc_info), exc_info, prefix='rhodecode-api')
313 error_headers = {
313 error_headers = {
314 'RhodeCode-Exception-Id': str(exc_id),
314 'RhodeCode-Exception-Id': str(exc_id),
315 'RhodeCode-Exception-Type': str(exc_type_name)
315 'RhodeCode-Exception-Type': str(exc_type_name)
316 }
316 }
317 err_resp = jsonrpc_error(
317 err_resp = jsonrpc_error(
318 request, retid=request.rpc_id, message='Internal server error',
318 request, retid=request.rpc_id, message='Internal server error',
319 headers=error_headers)
319 headers=error_headers)
320 if statsd:
320 if statsd:
321 statsd.incr('rhodecode_api_call_fail_total')
321 statsd.incr('rhodecode_api_call_fail_total')
322 return err_resp
322 return err_resp
323
323
324
324
325 def setup_request(request):
325 def setup_request(request):
326 """
326 """
327 Parse a JSON-RPC request body. It's used inside the predicates method
327 Parse a JSON-RPC request body. It's used inside the predicates method
328 to validate and bootstrap requests for usage in rpc calls.
328 to validate and bootstrap requests for usage in rpc calls.
329
329
330 We need to raise JSONRPCError here if we want to return some errors back to
330 We need to raise JSONRPCError here if we want to return some errors back to
331 user.
331 user.
332 """
332 """
333
333
334 log.debug('Executing setup request: %r', request)
334 log.debug('Executing setup request: %r', request)
335 request.rpc_ip_addr = get_ip_addr(request.environ)
335 request.rpc_ip_addr = get_ip_addr(request.environ)
336 # TODO(marcink): deprecate GET at some point
336 # TODO(marcink): deprecate GET at some point
337 if request.method not in ['POST', 'GET']:
337 if request.method not in ['POST', 'GET']:
338 log.debug('unsupported request method "%s"', request.method)
338 log.debug('unsupported request method "%s"', request.method)
339 raise JSONRPCError(
339 raise JSONRPCError(
340 'unsupported request method "%s". Please use POST' % request.method)
340 'unsupported request method "%s". Please use POST' % request.method)
341
341
342 if 'CONTENT_LENGTH' not in request.environ:
342 if 'CONTENT_LENGTH' not in request.environ:
343 log.debug("No Content-Length")
343 log.debug("No Content-Length")
344 raise JSONRPCError("Empty body, No Content-Length in request")
344 raise JSONRPCError("Empty body, No Content-Length in request")
345
345
346 else:
346 else:
347 length = request.environ['CONTENT_LENGTH']
347 length = request.environ['CONTENT_LENGTH']
348 log.debug('Content-Length: %s', length)
348 log.debug('Content-Length: %s', length)
349
349
350 if length == 0:
350 if length == 0:
351 log.debug("Content-Length is 0")
351 log.debug("Content-Length is 0")
352 raise JSONRPCError("Content-Length is 0")
352 raise JSONRPCError("Content-Length is 0")
353
353
354 raw_body = request.body
354 raw_body = request.body
355 log.debug("Loading JSON body now")
355 log.debug("Loading JSON body now")
356 try:
356 try:
357 json_body = ext_json.json.loads(raw_body)
357 json_body = ext_json.json.loads(raw_body)
358 except ValueError as e:
358 except ValueError as e:
359 # catch JSON errors Here
359 # catch JSON errors Here
360 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
360 raise JSONRPCError("JSON parse error ERR:%s RAW:%r" % (e, raw_body))
361
361
362 request.rpc_id = json_body.get('id')
362 request.rpc_id = json_body.get('id')
363 request.rpc_method = json_body.get('method')
363 request.rpc_method = json_body.get('method')
364
364
365 # check required base parameters
365 # check required base parameters
366 try:
366 try:
367 api_key = json_body.get('api_key')
367 api_key = json_body.get('api_key')
368 if not api_key:
368 if not api_key:
369 api_key = json_body.get('auth_token')
369 api_key = json_body.get('auth_token')
370
370
371 if not api_key:
371 if not api_key:
372 raise KeyError('api_key or auth_token')
372 raise KeyError('api_key or auth_token')
373
373
374 # TODO(marcink): support passing in token in request header
374 # TODO(marcink): support passing in token in request header
375
375
376 request.rpc_api_key = api_key
376 request.rpc_api_key = api_key
377 request.rpc_id = json_body['id']
377 request.rpc_id = json_body['id']
378 request.rpc_method = json_body['method']
378 request.rpc_method = json_body['method']
379 request.rpc_params = json_body['args'] \
379 request.rpc_params = json_body['args'] \
380 if isinstance(json_body['args'], dict) else {}
380 if isinstance(json_body['args'], dict) else {}
381
381
382 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
382 log.debug('method: %s, params: %.10240r', request.rpc_method, request.rpc_params)
383 except KeyError as e:
383 except KeyError as e:
384 raise JSONRPCError(f'Incorrect JSON data. Missing {e}')
384 raise JSONRPCError(f'Incorrect JSON data. Missing {e}')
385
385
386 log.debug('setup complete, now handling method:%s rpcid:%s',
386 log.debug('setup complete, now handling method:%s rpcid:%s',
387 request.rpc_method, request.rpc_id, )
387 request.rpc_method, request.rpc_id, )
388
388
389
389
390 class RoutePredicate(object):
390 class RoutePredicate(object):
391 def __init__(self, val, config):
391 def __init__(self, val, config):
392 self.val = val
392 self.val = val
393
393
394 def text(self):
394 def text(self):
395 return f'jsonrpc route = {self.val}'
395 return f'jsonrpc route = {self.val}'
396
396
397 phash = text
397 phash = text
398
398
399 def __call__(self, info, request):
399 def __call__(self, info, request):
400 if self.val:
400 if self.val:
401 # potentially setup and bootstrap our call
401 # potentially setup and bootstrap our call
402 setup_request(request)
402 setup_request(request)
403
403
404 # Always return True so that even if it isn't a valid RPC it
404 # Always return True so that even if it isn't a valid RPC it
405 # will fall through to the underlaying handlers like notfound_view
405 # will fall through to the underlaying handlers like notfound_view
406 return True
406 return True
407
407
408
408
409 class NotFoundPredicate(object):
409 class NotFoundPredicate(object):
410 def __init__(self, val, config):
410 def __init__(self, val, config):
411 self.val = val
411 self.val = val
412 self.methods = config.registry.jsonrpc_methods
412 self.methods = config.registry.jsonrpc_methods
413
413
414 def text(self):
414 def text(self):
415 return f'jsonrpc method not found = {self.val}'
415 return f'jsonrpc method not found = {self.val}'
416
416
417 phash = text
417 phash = text
418
418
419 def __call__(self, info, request):
419 def __call__(self, info, request):
420 return hasattr(request, 'rpc_method')
420 return hasattr(request, 'rpc_method')
421
421
422
422
423 class MethodPredicate(object):
423 class MethodPredicate(object):
424 def __init__(self, val, config):
424 def __init__(self, val, config):
425 self.method = val
425 self.method = val
426
426
427 def text(self):
427 def text(self):
428 return f'jsonrpc method = {self.method}'
428 return f'jsonrpc method = {self.method}'
429
429
430 phash = text
430 phash = text
431
431
432 def __call__(self, context, request):
432 def __call__(self, context, request):
433 # we need to explicitly return False here, so pyramid doesn't try to
433 # we need to explicitly return False here, so pyramid doesn't try to
434 # execute our view directly. We need our main handler to execute things
434 # execute our view directly. We need our main handler to execute things
435 return getattr(request, 'rpc_method') == self.method
435 return getattr(request, 'rpc_method') == self.method
436
436
437
437
438 def add_jsonrpc_method(config, view, **kwargs):
438 def add_jsonrpc_method(config, view, **kwargs):
439 # pop the method name
439 # pop the method name
440 method = kwargs.pop('method', None)
440 method = kwargs.pop('method', None)
441
441
442 if method is None:
442 if method is None:
443 raise ConfigurationError(
443 raise ConfigurationError(
444 'Cannot register a JSON-RPC method without specifying the "method"')
444 'Cannot register a JSON-RPC method without specifying the "method"')
445
445
446 # we define custom predicate, to enable to detect conflicting methods,
446 # we define custom predicate, to enable to detect conflicting methods,
447 # those predicates are kind of "translation" from the decorator variables
447 # those predicates are kind of "translation" from the decorator variables
448 # to internal predicates names
448 # to internal predicates names
449
449
450 kwargs['jsonrpc_method'] = method
450 kwargs['jsonrpc_method'] = method
451
451
452 # register our view into global view store for validation
452 # register our view into global view store for validation
453 config.registry.jsonrpc_methods[method] = view
453 config.registry.jsonrpc_methods[method] = view
454
454
455 # we're using our main request_view handler, here, so each method
455 # we're using our main request_view handler, here, so each method
456 # has a unified handler for itself
456 # has a unified handler for itself
457 config.add_view(request_view, route_name='apiv2', **kwargs)
457 config.add_view(request_view, route_name='apiv2', **kwargs)
458
458
459
459
460 class jsonrpc_method(object):
460 class jsonrpc_method(object):
461 """
461 """
462 decorator that works similar to @add_view_config decorator,
462 decorator that works similar to @add_view_config decorator,
463 but tailored for our JSON RPC
463 but tailored for our JSON RPC
464 """
464 """
465
465
466 venusian = venusian # for testing injection
466 venusian = venusian # for testing injection
467
467
468 def __init__(self, method=None, **kwargs):
468 def __init__(self, method=None, **kwargs):
469 self.method = method
469 self.method = method
470 self.kwargs = kwargs
470 self.kwargs = kwargs
471
471
472 def __call__(self, wrapped):
472 def __call__(self, wrapped):
473 kwargs = self.kwargs.copy()
473 kwargs = self.kwargs.copy()
474 kwargs['method'] = self.method or wrapped.__name__
474 kwargs['method'] = self.method or wrapped.__name__
475 depth = kwargs.pop('_depth', 0)
475 depth = kwargs.pop('_depth', 0)
476
476
477 def callback(context, name, ob):
477 def callback(context, name, ob):
478 config = context.config.with_package(info.module)
478 config = context.config.with_package(info.module)
479 config.add_jsonrpc_method(view=ob, **kwargs)
479 config.add_jsonrpc_method(view=ob, **kwargs)
480
480
481 info = venusian.attach(wrapped, callback, category='pyramid',
481 info = venusian.attach(wrapped, callback, category='pyramid',
482 depth=depth + 1)
482 depth=depth + 1)
483 if info.scope == 'class':
483 if info.scope == 'class':
484 # ensure that attr is set if decorating a class method
484 # ensure that attr is set if decorating a class method
485 kwargs.setdefault('attr', wrapped.__name__)
485 kwargs.setdefault('attr', wrapped.__name__)
486
486
487 kwargs['_info'] = info.codeinfo # fbo action_method
487 kwargs['_info'] = info.codeinfo # fbo action_method
488 return wrapped
488 return wrapped
489
489
490
490
491 class jsonrpc_deprecated_method(object):
491 class jsonrpc_deprecated_method(object):
492 """
492 """
493 Marks method as deprecated, adds log.warning, and inject special key to
493 Marks method as deprecated, adds log.warning, and inject special key to
494 the request variable to mark method as deprecated.
494 the request variable to mark method as deprecated.
495 Also injects special docstring that extract_docs will catch to mark
495 Also injects special docstring that extract_docs will catch to mark
496 method as deprecated.
496 method as deprecated.
497
497
498 :param use_method: specify which method should be used instead of
498 :param use_method: specify which method should be used instead of
499 the decorated one
499 the decorated one
500
500
501 Use like::
501 Use like::
502
502
503 @jsonrpc_method()
503 @jsonrpc_method()
504 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
504 @jsonrpc_deprecated_method(use_method='new_func', deprecated_at_version='3.0.0')
505 def old_func(request, apiuser, arg1, arg2):
505 def old_func(request, apiuser, arg1, arg2):
506 ...
506 ...
507 """
507 """
508
508
509 def __init__(self, use_method, deprecated_at_version):
509 def __init__(self, use_method, deprecated_at_version):
510 self.use_method = use_method
510 self.use_method = use_method
511 self.deprecated_at_version = deprecated_at_version
511 self.deprecated_at_version = deprecated_at_version
512 self.deprecated_msg = ''
512 self.deprecated_msg = ''
513
513
514 def __call__(self, func):
514 def __call__(self, func):
515 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
515 self.deprecated_msg = 'Please use method `{method}` instead.'.format(
516 method=self.use_method)
516 method=self.use_method)
517
517
518 docstring = """\n
518 docstring = """\n
519 .. deprecated:: {version}
519 .. deprecated:: {version}
520
520
521 {deprecation_message}
521 {deprecation_message}
522
522
523 {original_docstring}
523 {original_docstring}
524 """
524 """
525 func.__doc__ = docstring.format(
525 func.__doc__ = docstring.format(
526 version=self.deprecated_at_version,
526 version=self.deprecated_at_version,
527 deprecation_message=self.deprecated_msg,
527 deprecation_message=self.deprecated_msg,
528 original_docstring=func.__doc__)
528 original_docstring=func.__doc__)
529 return decorator.decorator(self.__wrapper, func)
529 return decorator.decorator(self.__wrapper, func)
530
530
531 def __wrapper(self, func, *fargs, **fkwargs):
531 def __wrapper(self, func, *fargs, **fkwargs):
532 log.warning('DEPRECATED API CALL on function %s, please '
532 log.warning('DEPRECATED API CALL on function %s, please '
533 'use `%s` instead', func, self.use_method)
533 'use `%s` instead', func, self.use_method)
534 # alter function docstring to mark as deprecated, this is picked up
534 # alter function docstring to mark as deprecated, this is picked up
535 # via fabric file that generates API DOC.
535 # via fabric file that generates API DOC.
536 result = func(*fargs, **fkwargs)
536 result = func(*fargs, **fkwargs)
537
537
538 request = fargs[0]
538 request = fargs[0]
539 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
539 request.rpc_deprecation = 'DEPRECATED METHOD ' + self.deprecated_msg
540 return result
540 return result
541
541
542
542
543 def add_api_methods(config):
543 def add_api_methods(config):
544 from rhodecode.api.views import (
544 from rhodecode.api.views import (
545 deprecated_api, gist_api, pull_request_api, repo_api, repo_group_api,
545 deprecated_api, gist_api, pull_request_api, repo_api, repo_group_api,
546 server_api, search_api, testing_api, user_api, user_group_api)
546 server_api, search_api, testing_api, user_api, user_group_api)
547
547
548 config.scan('rhodecode.api.views')
548 config.scan('rhodecode.api.views')
549
549
550
550
551 def includeme(config):
551 def includeme(config):
552 plugin_module = 'rhodecode.api'
552 plugin_module = 'rhodecode.api'
553 plugin_settings = get_plugin_settings(
553 plugin_settings = get_plugin_settings(
554 plugin_module, config.registry.settings)
554 plugin_module, config.registry.settings)
555
555
556 if not hasattr(config.registry, 'jsonrpc_methods'):
556 if not hasattr(config.registry, 'jsonrpc_methods'):
557 config.registry.jsonrpc_methods = OrderedDict()
557 config.registry.jsonrpc_methods = OrderedDict()
558
558
559 # match filter by given method only
559 # match filter by given method only
560 config.add_view_predicate('jsonrpc_method', MethodPredicate)
560 config.add_view_predicate('jsonrpc_method', MethodPredicate)
561 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
561 config.add_view_predicate('jsonrpc_method_not_found', NotFoundPredicate)
562
562
563 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer())
563 config.add_renderer(DEFAULT_RENDERER, ExtJsonRenderer())
564 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
564 config.add_directive('add_jsonrpc_method', add_jsonrpc_method)
565
565
566 config.add_route_predicate(
566 config.add_route_predicate(
567 'jsonrpc_call', RoutePredicate)
567 'jsonrpc_call', RoutePredicate)
568
568
569 config.add_route(
569 config.add_route(
570 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
570 'apiv2', plugin_settings.get('url', DEFAULT_URL), jsonrpc_call=True)
571
571
572 # register some exception handling view
572 # register some exception handling view
573 config.add_view(exception_view, context=JSONRPCBaseError)
573 config.add_view(exception_view, context=JSONRPCBaseError)
574 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
574 config.add_notfound_view(exception_view, jsonrpc_method_not_found=True)
575
575
576 add_api_methods(config)
576 add_api_methods(config)
@@ -1,42 +1,42 b''
1
1
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 class JSONRPCBaseError(Exception):
22 class JSONRPCBaseError(Exception):
23 def __init__(self, message='', *args):
23 def __init__(self, message='', *args):
24 self.message = message
24 self.message = message
25 super(JSONRPCBaseError, self).__init__(message, *args)
25 super(JSONRPCBaseError, self).__init__(message, *args)
26
26
27
27
28 class JSONRPCError(JSONRPCBaseError):
28 class JSONRPCError(JSONRPCBaseError):
29 pass
29 pass
30
30
31
31
32 class JSONRPCValidationError(JSONRPCBaseError):
32 class JSONRPCValidationError(JSONRPCBaseError):
33
33
34 def __init__(self, *args, **kwargs):
34 def __init__(self, *args, **kwargs):
35 self.colander_exception = kwargs.pop('colander_exc')
35 self.colander_exception = kwargs.pop('colander_exc')
36 super(JSONRPCValidationError, self).__init__(
36 super(JSONRPCValidationError, self).__init__(
37 message=self.colander_exception, *args)
37 message=self.colander_exception, *args)
38
38
39
39
40 class JSONRPCForbidden(JSONRPCBaseError):
40 class JSONRPCForbidden(JSONRPCBaseError):
41 pass
41 pass
42
42
@@ -1,18 +1,18 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,51 +1,51 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.meta import Session
22 from rhodecode.model.meta import Session
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.model.auth_token import AuthTokenModel
24 from rhodecode.model.auth_token import AuthTokenModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26
26
27
27
28 @pytest.fixture(scope="class")
28 @pytest.fixture(scope="class")
29 def testuser_api(request, baseapp):
29 def testuser_api(request, baseapp):
30 cls = request.cls
30 cls = request.cls
31
31
32 # ADMIN USER
32 # ADMIN USER
33 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
33 cls.usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
34 cls.apikey = cls.usr.api_key
34 cls.apikey = cls.usr.api_key
35
35
36 # REGULAR USER
36 # REGULAR USER
37 cls.test_user = UserModel().create_or_update(
37 cls.test_user = UserModel().create_or_update(
38 username='test-api',
38 username='test-api',
39 password='test',
39 password='test',
40 email='test@api.rhodecode.org',
40 email='test@api.rhodecode.org',
41 firstname='first',
41 firstname='first',
42 lastname='last'
42 lastname='last'
43 )
43 )
44 # create TOKEN for user, if he doesn't have one
44 # create TOKEN for user, if he doesn't have one
45 if not cls.test_user.api_key:
45 if not cls.test_user.api_key:
46 AuthTokenModel().create(
46 AuthTokenModel().create(
47 user=cls.test_user, description=u'TEST_USER_TOKEN')
47 user=cls.test_user, description=u'TEST_USER_TOKEN')
48
48
49 Session().commit()
49 Session().commit()
50 cls.TEST_USER_LOGIN = cls.test_user.username
50 cls.TEST_USER_LOGIN = cls.test_user.username
51 cls.apikey_regular = cls.test_user.api_key
51 cls.apikey_regular = cls.test_user.api_key
@@ -1,61 +1,61 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.db import Repository, RepositoryField
22 from rhodecode.model.db import Repository, RepositoryField
23 from rhodecode.api.tests.utils import (
23 from rhodecode.api.tests.utils import (
24 build_data, api_call, assert_ok, assert_error)
24 build_data, api_call, assert_ok, assert_error)
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestAddFieldToRepo(object):
28 class TestAddFieldToRepo(object):
29 def test_api_add_field_to_repo(self, backend):
29 def test_api_add_field_to_repo(self, backend):
30 repo = backend.create_repo()
30 repo = backend.create_repo()
31 repo_name = repo.repo_name
31 repo_name = repo.repo_name
32 id_, params = build_data(
32 id_, params = build_data(
33 self.apikey, 'add_field_to_repo',
33 self.apikey, 'add_field_to_repo',
34 repoid=repo_name,
34 repoid=repo_name,
35 key='extra_field',
35 key='extra_field',
36 label='extra_field_label',
36 label='extra_field_label',
37 description='extra_field_desc')
37 description='extra_field_desc')
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39 expected = {
39 expected = {
40 'msg': 'Added new repository field `extra_field`',
40 'msg': 'Added new repository field `extra_field`',
41 'success': True,
41 'success': True,
42 }
42 }
43 assert_ok(id_, expected, given=response.body)
43 assert_ok(id_, expected, given=response.body)
44
44
45 repo = Repository.get_by_repo_name(repo_name)
45 repo = Repository.get_by_repo_name(repo_name)
46 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
46 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
47 _data = repo_field.get_dict()
47 _data = repo_field.get_dict()
48 assert _data['field_desc'] == 'extra_field_desc'
48 assert _data['field_desc'] == 'extra_field_desc'
49 assert _data['field_key'] == 'extra_field'
49 assert _data['field_key'] == 'extra_field'
50 assert _data['field_label'] == 'extra_field_label'
50 assert _data['field_label'] == 'extra_field_label'
51
51
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'add_field_to_repo',
53 self.apikey, 'add_field_to_repo',
54 repoid=repo_name,
54 repoid=repo_name,
55 key='extra_field',
55 key='extra_field',
56 label='extra_field_label',
56 label='extra_field_label',
57 description='extra_field_desc')
57 description='extra_field_desc')
58 response = api_call(self.app, params)
58 response = api_call(self.app, params)
59 expected = 'Field with key `extra_field` exists for repo `%s`' % (
59 expected = 'Field with key `extra_field` exists for repo `%s`' % (
60 repo_name)
60 repo_name)
61 assert_error(id_, expected, given=response.body)
61 assert_error(id_, expected, given=response.body)
@@ -1,71 +1,71 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user_group import UserGroupModel
23 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestAddUserToUserGroup(object):
29 class TestAddUserToUserGroup(object):
30 def test_api_add_user_to_user_group(self, user_util):
30 def test_api_add_user_to_user_group(self, user_util):
31 group = user_util.create_user_group()
31 group = user_util.create_user_group()
32 user = user_util.create_user()
32 user = user_util.create_user()
33 group_name = group.users_group_name
33 group_name = group.users_group_name
34 user_name = user.username
34 user_name = user.username
35 id_, params = build_data(
35 id_, params = build_data(
36 self.apikey, 'add_user_to_user_group',
36 self.apikey, 'add_user_to_user_group',
37 usergroupid=group_name, userid=user_name)
37 usergroupid=group_name, userid=user_name)
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39 expected = {
39 expected = {
40 'msg': 'added member `%s` to user group `%s`' % (
40 'msg': 'added member `%s` to user group `%s`' % (
41 user_name, group_name
41 user_name, group_name
42 ),
42 ),
43 'success': True
43 'success': True
44 }
44 }
45 assert_ok(id_, expected, given=response.body)
45 assert_ok(id_, expected, given=response.body)
46
46
47 def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util):
47 def test_api_add_user_to_user_group_that_doesnt_exist(self, user_util):
48 user = user_util.create_user()
48 user = user_util.create_user()
49 user_name = user.username
49 user_name = user.username
50 id_, params = build_data(
50 id_, params = build_data(
51 self.apikey, 'add_user_to_user_group',
51 self.apikey, 'add_user_to_user_group',
52 usergroupid='false-group',
52 usergroupid='false-group',
53 userid=user_name)
53 userid=user_name)
54 response = api_call(self.app, params)
54 response = api_call(self.app, params)
55
55
56 expected = 'user group `%s` does not exist' % 'false-group'
56 expected = 'user group `%s` does not exist' % 'false-group'
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
58
58
59 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
59 @mock.patch.object(UserGroupModel, 'add_user_to_group', crash)
60 def test_api_add_user_to_user_group_exception_occurred(self, user_util):
60 def test_api_add_user_to_user_group_exception_occurred(self, user_util):
61 group = user_util.create_user_group()
61 group = user_util.create_user_group()
62 user = user_util.create_user()
62 user = user_util.create_user()
63 group_name = group.users_group_name
63 group_name = group.users_group_name
64 user_name = user.username
64 user_name = user.username
65 id_, params = build_data(
65 id_, params = build_data(
66 self.apikey, 'add_user_to_user_group',
66 self.apikey, 'add_user_to_user_group',
67 usergroupid=group_name, userid=user_name)
67 usergroupid=group_name, userid=user_name)
68 response = api_call(self.app, params)
68 response = api_call(self.app, params)
69
69
70 expected = 'failed to add member to user group `%s`' % (group_name,)
70 expected = 'failed to add member to user group `%s`' % (group_name,)
71 assert_error(id_, expected, given=response.body)
71 assert_error(id_, expected, given=response.body)
@@ -1,133 +1,133 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.api.utils import Optional, OAttr
22 from rhodecode.api.utils import Optional, OAttr
23 from rhodecode.api.tests.utils import (
23 from rhodecode.api.tests.utils import (
24 build_data, api_call, assert_error, assert_ok)
24 build_data, api_call, assert_error, assert_ok)
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestApi(object):
28 class TestApi(object):
29 maxDiff = None
29 maxDiff = None
30
30
31 def test_Optional_object(self):
31 def test_Optional_object(self):
32
32
33 option1 = Optional(None)
33 option1 = Optional(None)
34 assert '<Optional:%s>' % (None,) == repr(option1)
34 assert '<Optional:%s>' % (None,) == repr(option1)
35 assert option1() is None
35 assert option1() is None
36
36
37 assert 1 == Optional.extract(Optional(1))
37 assert 1 == Optional.extract(Optional(1))
38 assert 'example' == Optional.extract('example')
38 assert 'example' == Optional.extract('example')
39
39
40 def test_Optional_OAttr(self):
40 def test_Optional_OAttr(self):
41 option1 = Optional(OAttr('apiuser'))
41 option1 = Optional(OAttr('apiuser'))
42 assert 'apiuser' == Optional.extract(option1)
42 assert 'apiuser' == Optional.extract(option1)
43
43
44 def test_OAttr_object(self):
44 def test_OAttr_object(self):
45 oattr1 = OAttr('apiuser')
45 oattr1 = OAttr('apiuser')
46 assert '<OptionalAttr:apiuser>' == repr(oattr1)
46 assert '<OptionalAttr:apiuser>' == repr(oattr1)
47 assert oattr1() == oattr1
47 assert oattr1() == oattr1
48
48
49 def test_api_wrong_key(self):
49 def test_api_wrong_key(self):
50 id_, params = build_data('trololo', 'get_user')
50 id_, params = build_data('trololo', 'get_user')
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52
52
53 expected = 'Invalid API KEY'
53 expected = 'Invalid API KEY'
54 assert_error(id_, expected, given=response.body)
54 assert_error(id_, expected, given=response.body)
55
55
56 def test_api_missing_non_optional_param(self):
56 def test_api_missing_non_optional_param(self):
57 id_, params = build_data(self.apikey, 'get_repo')
57 id_, params = build_data(self.apikey, 'get_repo')
58 response = api_call(self.app, params)
58 response = api_call(self.app, params)
59
59
60 expected = 'Missing non optional `repoid` arg in JSON DATA'
60 expected = 'Missing non optional `repoid` arg in JSON DATA'
61 assert_error(id_, expected, given=response.body)
61 assert_error(id_, expected, given=response.body)
62
62
63 def test_api_missing_non_optional_param_args_null(self):
63 def test_api_missing_non_optional_param_args_null(self):
64 id_, params = build_data(self.apikey, 'get_repo')
64 id_, params = build_data(self.apikey, 'get_repo')
65 params = params.replace(b'"args": {}', b'"args": null')
65 params = params.replace(b'"args": {}', b'"args": null')
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = 'Missing non optional `repoid` arg in JSON DATA'
68 expected = 'Missing non optional `repoid` arg in JSON DATA'
69 assert_error(id_, expected, given=response.body)
69 assert_error(id_, expected, given=response.body)
70
70
71 def test_api_missing_non_optional_param_args_bad(self):
71 def test_api_missing_non_optional_param_args_bad(self):
72 id_, params = build_data(self.apikey, 'get_repo')
72 id_, params = build_data(self.apikey, 'get_repo')
73 params = params.replace(b'"args": {}', b'"args": 1')
73 params = params.replace(b'"args": {}', b'"args": 1')
74 response = api_call(self.app, params)
74 response = api_call(self.app, params)
75
75
76 expected = 'Missing non optional `repoid` arg in JSON DATA'
76 expected = 'Missing non optional `repoid` arg in JSON DATA'
77 assert_error(id_, expected, given=response.body)
77 assert_error(id_, expected, given=response.body)
78
78
79 def test_api_non_existing_method(self, request):
79 def test_api_non_existing_method(self, request):
80 id_, params = build_data(self.apikey, 'not_existing', args='xx')
80 id_, params = build_data(self.apikey, 'not_existing', args='xx')
81 response = api_call(self.app, params)
81 response = api_call(self.app, params)
82 expected = 'No such method: not_existing. Similar methods: none'
82 expected = 'No such method: not_existing. Similar methods: none'
83 assert_error(id_, expected, given=response.body)
83 assert_error(id_, expected, given=response.body)
84
84
85 def test_api_non_existing_method_have_similar(self, request):
85 def test_api_non_existing_method_have_similar(self, request):
86 id_, params = build_data(self.apikey, 'comment', args='xx')
86 id_, params = build_data(self.apikey, 'comment', args='xx')
87 response = api_call(self.app, params)
87 response = api_call(self.app, params)
88 expected = 'No such method: comment. ' \
88 expected = 'No such method: comment. ' \
89 'Similar methods: changeset_comment, comment_pull_request, ' \
89 'Similar methods: changeset_comment, comment_pull_request, ' \
90 'get_pull_request_comments, comment_commit, edit_comment, ' \
90 'get_pull_request_comments, comment_commit, edit_comment, ' \
91 'get_comment, get_repo_comments'
91 'get_comment, get_repo_comments'
92 assert_error(id_, expected, given=response.body)
92 assert_error(id_, expected, given=response.body)
93
93
94 def test_api_disabled_user(self, request):
94 def test_api_disabled_user(self, request):
95
95
96 def set_active(active):
96 def set_active(active):
97 from rhodecode.model.db import Session, User
97 from rhodecode.model.db import Session, User
98 user = User.get_by_auth_token(self.apikey)
98 user = User.get_by_auth_token(self.apikey)
99 user.active = active
99 user.active = active
100 Session().add(user)
100 Session().add(user)
101 Session().commit()
101 Session().commit()
102
102
103 request.addfinalizer(lambda: set_active(True))
103 request.addfinalizer(lambda: set_active(True))
104
104
105 set_active(False)
105 set_active(False)
106 id_, params = build_data(self.apikey, 'test', args='xx')
106 id_, params = build_data(self.apikey, 'test', args='xx')
107 response = api_call(self.app, params)
107 response = api_call(self.app, params)
108 expected = 'Request from this user not allowed'
108 expected = 'Request from this user not allowed'
109 assert_error(id_, expected, given=response.body)
109 assert_error(id_, expected, given=response.body)
110
110
111 def test_api_args_is_null(self):
111 def test_api_args_is_null(self):
112 __, params = build_data(self.apikey, 'get_users', )
112 __, params = build_data(self.apikey, 'get_users', )
113 params = params.replace(b'"args": {}', b'"args": null')
113 params = params.replace(b'"args": {}', b'"args": null')
114 response = api_call(self.app, params)
114 response = api_call(self.app, params)
115 assert response.status == '200 OK'
115 assert response.status == '200 OK'
116
116
117 def test_api_args_is_bad(self):
117 def test_api_args_is_bad(self):
118 __, params = build_data(self.apikey, 'get_users', )
118 __, params = build_data(self.apikey, 'get_users', )
119 params = params.replace(b'"args": {}', b'"args": 1')
119 params = params.replace(b'"args": {}', b'"args": 1')
120 response = api_call(self.app, params)
120 response = api_call(self.app, params)
121 assert response.status == '200 OK'
121 assert response.status == '200 OK'
122
122
123 def test_api_args_different_args(self):
123 def test_api_args_different_args(self):
124 import string
124 import string
125 expected = {
125 expected = {
126 'ascii_letters': string.ascii_letters,
126 'ascii_letters': string.ascii_letters,
127 'ws': string.whitespace,
127 'ws': string.whitespace,
128 'printables': string.printable
128 'printables': string.printable
129 }
129 }
130 id_, params = build_data(self.apikey, 'test', args=expected)
130 id_, params = build_data(self.apikey, 'test', args=expected)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132 assert response.status == '200 OK'
132 assert response.status == '200 OK'
133 assert_ok(id_, expected, response.body)
133 assert_ok(id_, expected, response.body)
@@ -1,44 +1,44 b''
1
1
2
2
3 # Copyright (C) 2017-2020 RhodeCode GmbH
3 # Copyright (C) 2017-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.lib.user_sessions import FileAuthSessions
24 from rhodecode.lib.user_sessions import FileAuthSessions
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, crash)
26 build_data, api_call, assert_ok, assert_error, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestCleanupSessions(object):
30 class TestCleanupSessions(object):
31 def test_api_cleanup_sessions(self):
31 def test_api_cleanup_sessions(self):
32 id_, params = build_data(self.apikey, 'cleanup_sessions')
32 id_, params = build_data(self.apikey, 'cleanup_sessions')
33 response = api_call(self.app, params)
33 response = api_call(self.app, params)
34
34
35 expected = {'backend': 'file sessions', 'sessions_removed': 0}
35 expected = {'backend': 'file sessions', 'sessions_removed': 0}
36 assert_ok(id_, expected, given=response.body)
36 assert_ok(id_, expected, given=response.body)
37
37
38 @mock.patch.object(FileAuthSessions, 'clean_sessions', crash)
38 @mock.patch.object(FileAuthSessions, 'clean_sessions', crash)
39 def test_api_cleanup_error(self):
39 def test_api_cleanup_error(self):
40 id_, params = build_data(self.apikey, 'cleanup_sessions', )
40 id_, params = build_data(self.apikey, 'cleanup_sessions', )
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = 'Error occurred during session cleanup'
43 expected = 'Error occurred during session cleanup'
44 assert_error(id_, expected, given=response.body)
44 assert_error(id_, expected, given=response.body)
@@ -1,112 +1,112 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.db import UserLog
22 from rhodecode.model.db import UserLog
23 from rhodecode.model.pull_request import PullRequestModel
23 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestClosePullRequest(object):
30 class TestClosePullRequest(object):
31
31
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_close_pull_request(self, pr_util):
33 def test_api_close_pull_request(self, pr_util):
34 pull_request = pr_util.create_pull_request()
34 pull_request = pr_util.create_pull_request()
35 pull_request_id = pull_request.pull_request_id
35 pull_request_id = pull_request.pull_request_id
36 author = pull_request.user_id
36 author = pull_request.user_id
37 repo = pull_request.target_repo.repo_id
37 repo = pull_request.target_repo.repo_id
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey, 'close_pull_request',
39 self.apikey, 'close_pull_request',
40 repoid=pull_request.target_repo.repo_name,
40 repoid=pull_request.target_repo.repo_name,
41 pullrequestid=pull_request.pull_request_id)
41 pullrequestid=pull_request.pull_request_id)
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43 expected = {
43 expected = {
44 'pull_request_id': pull_request_id,
44 'pull_request_id': pull_request_id,
45 'close_status': 'Rejected',
45 'close_status': 'Rejected',
46 'closed': True,
46 'closed': True,
47 }
47 }
48 assert_ok(id_, expected, response.body)
48 assert_ok(id_, expected, response.body)
49 journal = UserLog.query()\
49 journal = UserLog.query()\
50 .filter(UserLog.user_id == author) \
50 .filter(UserLog.user_id == author) \
51 .order_by(UserLog.user_log_id.asc()) \
51 .order_by(UserLog.user_log_id.asc()) \
52 .filter(UserLog.repository_id == repo)\
52 .filter(UserLog.repository_id == repo)\
53 .all()
53 .all()
54 assert journal[-1].action == 'repo.pull_request.close'
54 assert journal[-1].action == 'repo.pull_request.close'
55
55
56 @pytest.mark.backends("git", "hg")
56 @pytest.mark.backends("git", "hg")
57 def test_api_close_pull_request_already_closed_error(self, pr_util):
57 def test_api_close_pull_request_already_closed_error(self, pr_util):
58 pull_request = pr_util.create_pull_request()
58 pull_request = pr_util.create_pull_request()
59 pull_request_id = pull_request.pull_request_id
59 pull_request_id = pull_request.pull_request_id
60 pull_request_repo = pull_request.target_repo.repo_name
60 pull_request_repo = pull_request.target_repo.repo_name
61 PullRequestModel().close_pull_request(
61 PullRequestModel().close_pull_request(
62 pull_request, pull_request.author)
62 pull_request, pull_request.author)
63 id_, params = build_data(
63 id_, params = build_data(
64 self.apikey, 'close_pull_request',
64 self.apikey, 'close_pull_request',
65 repoid=pull_request_repo, pullrequestid=pull_request_id)
65 repoid=pull_request_repo, pullrequestid=pull_request_id)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = 'pull request `%s` is already closed' % pull_request_id
68 expected = 'pull request `%s` is already closed' % pull_request_id
69 assert_error(id_, expected, given=response.body)
69 assert_error(id_, expected, given=response.body)
70
70
71 @pytest.mark.backends("git", "hg")
71 @pytest.mark.backends("git", "hg")
72 def test_api_close_pull_request_repo_error(self, pr_util):
72 def test_api_close_pull_request_repo_error(self, pr_util):
73 pull_request = pr_util.create_pull_request()
73 pull_request = pr_util.create_pull_request()
74 id_, params = build_data(
74 id_, params = build_data(
75 self.apikey, 'close_pull_request',
75 self.apikey, 'close_pull_request',
76 repoid=666, pullrequestid=pull_request.pull_request_id)
76 repoid=666, pullrequestid=pull_request.pull_request_id)
77 response = api_call(self.app, params)
77 response = api_call(self.app, params)
78
78
79 expected = 'repository `666` does not exist'
79 expected = 'repository `666` does not exist'
80 assert_error(id_, expected, given=response.body)
80 assert_error(id_, expected, given=response.body)
81
81
82 @pytest.mark.backends("git", "hg")
82 @pytest.mark.backends("git", "hg")
83 def test_api_close_pull_request_non_admin_with_userid_error(self,
83 def test_api_close_pull_request_non_admin_with_userid_error(self,
84 pr_util):
84 pr_util):
85 pull_request = pr_util.create_pull_request()
85 pull_request = pr_util.create_pull_request()
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey_regular, 'close_pull_request',
87 self.apikey_regular, 'close_pull_request',
88 repoid=pull_request.target_repo.repo_name,
88 repoid=pull_request.target_repo.repo_name,
89 pullrequestid=pull_request.pull_request_id,
89 pullrequestid=pull_request.pull_request_id,
90 userid=TEST_USER_ADMIN_LOGIN)
90 userid=TEST_USER_ADMIN_LOGIN)
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92
92
93 expected = 'userid is not the same as your user'
93 expected = 'userid is not the same as your user'
94 assert_error(id_, expected, given=response.body)
94 assert_error(id_, expected, given=response.body)
95
95
96 @pytest.mark.backends("git", "hg")
96 @pytest.mark.backends("git", "hg")
97 def test_api_close_pull_request_no_perms_to_close(
97 def test_api_close_pull_request_no_perms_to_close(
98 self, user_util, pr_util):
98 self, user_util, pr_util):
99 user = user_util.create_user()
99 user = user_util.create_user()
100 pull_request = pr_util.create_pull_request()
100 pull_request = pr_util.create_pull_request()
101
101
102 id_, params = build_data(
102 id_, params = build_data(
103 user.api_key, 'close_pull_request',
103 user.api_key, 'close_pull_request',
104 repoid=pull_request.target_repo.repo_name,
104 repoid=pull_request.target_repo.repo_name,
105 pullrequestid=pull_request.pull_request_id,)
105 pullrequestid=pull_request.pull_request_id,)
106 response = api_call(self.app, params)
106 response = api_call(self.app, params)
107
107
108 expected = ('pull request `%s` close failed, '
108 expected = ('pull request `%s` close failed, '
109 'no permission to close.') % pull_request.pull_request_id
109 'no permission to close.') % pull_request.pull_request_id
110
110
111 response_json = response.json['error']
111 response_json = response.json['error']
112 assert response_json == expected
112 assert response_json == expected
@@ -1,115 +1,115 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.db import ChangesetStatus, User
22 from rhodecode.model.db import ChangesetStatus, User
23 from rhodecode.api.tests.utils import (
23 from rhodecode.api.tests.utils import (
24 build_data, api_call, assert_error, assert_ok)
24 build_data, api_call, assert_error, assert_ok)
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestCommentCommit(object):
28 class TestCommentCommit(object):
29 def test_api_comment_commit_on_empty_repo(self, backend):
29 def test_api_comment_commit_on_empty_repo(self, backend):
30 repo = backend.create_repo()
30 repo = backend.create_repo()
31 id_, params = build_data(
31 id_, params = build_data(
32 self.apikey, 'comment_commit', repoid=repo.repo_name,
32 self.apikey, 'comment_commit', repoid=repo.repo_name,
33 commit_id='tip', message='message', status_change=None)
33 commit_id='tip', message='message', status_change=None)
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35 expected = 'There are no commits yet'
35 expected = 'There are no commits yet'
36 assert_error(id_, expected, given=response.body)
36 assert_error(id_, expected, given=response.body)
37
37
38 @pytest.mark.parametrize("commit_id, expected_err", [
38 @pytest.mark.parametrize("commit_id, expected_err", [
39 ('abcabca', {'hg': 'Commit {commit} does not exist for `{repo}`',
39 ('abcabca', {'hg': 'Commit {commit} does not exist for `{repo}`',
40 'git': 'Commit {commit} does not exist for `{repo}`',
40 'git': 'Commit {commit} does not exist for `{repo}`',
41 'svn': 'Commit id {commit} not understood.'}),
41 'svn': 'Commit id {commit} not understood.'}),
42 ('idontexist', {'hg': 'Commit {commit} does not exist for `{repo}`',
42 ('idontexist', {'hg': 'Commit {commit} does not exist for `{repo}`',
43 'git': 'Commit {commit} does not exist for `{repo}`',
43 'git': 'Commit {commit} does not exist for `{repo}`',
44 'svn': 'Commit id {commit} not understood.'}),
44 'svn': 'Commit id {commit} not understood.'}),
45 ])
45 ])
46 def test_api_comment_commit_wrong_hash(self, backend, commit_id, expected_err):
46 def test_api_comment_commit_wrong_hash(self, backend, commit_id, expected_err):
47 repo_name = backend.repo.repo_name
47 repo_name = backend.repo.repo_name
48 id_, params = build_data(
48 id_, params = build_data(
49 self.apikey, 'comment_commit', repoid=repo_name,
49 self.apikey, 'comment_commit', repoid=repo_name,
50 commit_id=commit_id, message='message', status_change=None)
50 commit_id=commit_id, message='message', status_change=None)
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52
52
53 expected_err = expected_err[backend.alias]
53 expected_err = expected_err[backend.alias]
54 expected_err = expected_err.format(
54 expected_err = expected_err.format(
55 repo=backend.repo.scm_instance().name, commit=commit_id)
55 repo=backend.repo.scm_instance().name, commit=commit_id)
56 assert_error(id_, expected_err, given=response.body)
56 assert_error(id_, expected_err, given=response.body)
57
57
58 @pytest.mark.parametrize("status_change, message, commit_id", [
58 @pytest.mark.parametrize("status_change, message, commit_id", [
59 (None, 'Hallo', 'tip'),
59 (None, 'Hallo', 'tip'),
60 (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'),
60 (ChangesetStatus.STATUS_APPROVED, 'Approved', 'tip'),
61 (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'),
61 (ChangesetStatus.STATUS_REJECTED, 'Rejected', 'tip'),
62 ])
62 ])
63 def test_api_comment_commit(
63 def test_api_comment_commit(
64 self, backend, status_change, message, commit_id,
64 self, backend, status_change, message, commit_id,
65 no_notifications):
65 no_notifications):
66
66
67 commit_id = backend.repo.scm_instance().get_commit(commit_id).raw_id
67 commit_id = backend.repo.scm_instance().get_commit(commit_id).raw_id
68
68
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'comment_commit', repoid=backend.repo_name,
70 self.apikey, 'comment_commit', repoid=backend.repo_name,
71 commit_id=commit_id, message=message, status=status_change)
71 commit_id=commit_id, message=message, status=status_change)
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73 repo = backend.repo.scm_instance()
73 repo = backend.repo.scm_instance()
74 expected = {
74 expected = {
75 'msg': 'Commented on commit `%s` for repository `%s`' % (
75 'msg': 'Commented on commit `%s` for repository `%s`' % (
76 repo.get_commit().raw_id, backend.repo_name),
76 repo.get_commit().raw_id, backend.repo_name),
77 'status_change': status_change,
77 'status_change': status_change,
78 'success': True
78 'success': True
79 }
79 }
80 assert_ok(id_, expected, given=response.body)
80 assert_ok(id_, expected, given=response.body)
81
81
82 def test_api_comment_commit_with_extra_recipients(self, backend, user_util):
82 def test_api_comment_commit_with_extra_recipients(self, backend, user_util):
83
83
84 commit_id = backend.repo.scm_instance().get_commit('tip').raw_id
84 commit_id = backend.repo.scm_instance().get_commit('tip').raw_id
85
85
86 user1 = user_util.create_user()
86 user1 = user_util.create_user()
87 user1_id = user1.user_id
87 user1_id = user1.user_id
88 user2 = user_util.create_user()
88 user2 = user_util.create_user()
89 user2_id = user2.user_id
89 user2_id = user2.user_id
90
90
91 id_, params = build_data(
91 id_, params = build_data(
92 self.apikey, 'comment_commit', repoid=backend.repo_name,
92 self.apikey, 'comment_commit', repoid=backend.repo_name,
93 commit_id=commit_id,
93 commit_id=commit_id,
94 message='abracadabra',
94 message='abracadabra',
95 extra_recipients=[user1.user_id, user2.username])
95 extra_recipients=[user1.user_id, user2.username])
96
96
97 response = api_call(self.app, params)
97 response = api_call(self.app, params)
98 repo = backend.repo.scm_instance()
98 repo = backend.repo.scm_instance()
99
99
100 expected = {
100 expected = {
101 'msg': 'Commented on commit `%s` for repository `%s`' % (
101 'msg': 'Commented on commit `%s` for repository `%s`' % (
102 repo.get_commit().raw_id, backend.repo_name),
102 repo.get_commit().raw_id, backend.repo_name),
103 'status_change': None,
103 'status_change': None,
104 'success': True
104 'success': True
105 }
105 }
106
106
107 assert_ok(id_, expected, given=response.body)
107 assert_ok(id_, expected, given=response.body)
108 # check user1/user2 inbox for notification
108 # check user1/user2 inbox for notification
109 user1 = User.get(user1_id)
109 user1 = User.get(user1_id)
110 assert 1 == len(user1.notifications)
110 assert 1 == len(user1.notifications)
111 assert 'abracadabra' in user1.notifications[0].notification.body
111 assert 'abracadabra' in user1.notifications[0].notification.body
112
112
113 user2 = User.get(user2_id)
113 user2 = User.get(user2_id)
114 assert 1 == len(user2.notifications)
114 assert 1 == len(user2.notifications)
115 assert 'abracadabra' in user2.notifications[0].notification.body
115 assert 'abracadabra' in user2.notifications[0].notification.body
@@ -1,380 +1,380 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.comment import CommentsModel
22 from rhodecode.model.comment import CommentsModel
23 from rhodecode.model.db import UserLog, User, ChangesetComment
23 from rhodecode.model.db import UserLog, User, ChangesetComment
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestCommentPullRequest(object):
31 class TestCommentPullRequest(object):
32 finalizers = []
32 finalizers = []
33
33
34 def teardown_method(self, method):
34 def teardown_method(self, method):
35 if self.finalizers:
35 if self.finalizers:
36 for finalizer in self.finalizers:
36 for finalizer in self.finalizers:
37 finalizer()
37 finalizer()
38 self.finalizers = []
38 self.finalizers = []
39
39
40 @pytest.mark.backends("git", "hg")
40 @pytest.mark.backends("git", "hg")
41 def test_api_comment_pull_request(self, pr_util, no_notifications):
41 def test_api_comment_pull_request(self, pr_util, no_notifications):
42 pull_request = pr_util.create_pull_request()
42 pull_request = pr_util.create_pull_request()
43 pull_request_id = pull_request.pull_request_id
43 pull_request_id = pull_request.pull_request_id
44 author = pull_request.user_id
44 author = pull_request.user_id
45 repo = pull_request.target_repo.repo_id
45 repo = pull_request.target_repo.repo_id
46 id_, params = build_data(
46 id_, params = build_data(
47 self.apikey, 'comment_pull_request',
47 self.apikey, 'comment_pull_request',
48 repoid=pull_request.target_repo.repo_name,
48 repoid=pull_request.target_repo.repo_name,
49 pullrequestid=pull_request.pull_request_id,
49 pullrequestid=pull_request.pull_request_id,
50 message='test message')
50 message='test message')
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52 pull_request = PullRequestModel().get(pull_request.pull_request_id)
52 pull_request = PullRequestModel().get(pull_request.pull_request_id)
53
53
54 comments = CommentsModel().get_comments(
54 comments = CommentsModel().get_comments(
55 pull_request.target_repo.repo_id, pull_request=pull_request)
55 pull_request.target_repo.repo_id, pull_request=pull_request)
56
56
57 expected = {
57 expected = {
58 'pull_request_id': pull_request.pull_request_id,
58 'pull_request_id': pull_request.pull_request_id,
59 'comment_id': comments[-1].comment_id,
59 'comment_id': comments[-1].comment_id,
60 'status': {'given': None, 'was_changed': None}
60 'status': {'given': None, 'was_changed': None}
61 }
61 }
62 assert_ok(id_, expected, response.body)
62 assert_ok(id_, expected, response.body)
63
63
64 journal = UserLog.query()\
64 journal = UserLog.query()\
65 .filter(UserLog.user_id == author)\
65 .filter(UserLog.user_id == author)\
66 .filter(UserLog.repository_id == repo) \
66 .filter(UserLog.repository_id == repo) \
67 .order_by(UserLog.user_log_id.asc()) \
67 .order_by(UserLog.user_log_id.asc()) \
68 .all()
68 .all()
69 assert journal[-1].action == 'repo.pull_request.comment.create'
69 assert journal[-1].action == 'repo.pull_request.comment.create'
70
70
71 @pytest.mark.backends("git", "hg")
71 @pytest.mark.backends("git", "hg")
72 def test_api_comment_pull_request_with_extra_recipients(self, pr_util, user_util):
72 def test_api_comment_pull_request_with_extra_recipients(self, pr_util, user_util):
73 pull_request = pr_util.create_pull_request()
73 pull_request = pr_util.create_pull_request()
74
74
75 user1 = user_util.create_user()
75 user1 = user_util.create_user()
76 user1_id = user1.user_id
76 user1_id = user1.user_id
77 user2 = user_util.create_user()
77 user2 = user_util.create_user()
78 user2_id = user2.user_id
78 user2_id = user2.user_id
79
79
80 id_, params = build_data(
80 id_, params = build_data(
81 self.apikey, 'comment_pull_request',
81 self.apikey, 'comment_pull_request',
82 repoid=pull_request.target_repo.repo_name,
82 repoid=pull_request.target_repo.repo_name,
83 pullrequestid=pull_request.pull_request_id,
83 pullrequestid=pull_request.pull_request_id,
84 message='test message',
84 message='test message',
85 extra_recipients=[user1.user_id, user2.username]
85 extra_recipients=[user1.user_id, user2.username]
86 )
86 )
87 response = api_call(self.app, params)
87 response = api_call(self.app, params)
88 pull_request = PullRequestModel().get(pull_request.pull_request_id)
88 pull_request = PullRequestModel().get(pull_request.pull_request_id)
89
89
90 comments = CommentsModel().get_comments(
90 comments = CommentsModel().get_comments(
91 pull_request.target_repo.repo_id, pull_request=pull_request)
91 pull_request.target_repo.repo_id, pull_request=pull_request)
92
92
93 expected = {
93 expected = {
94 'pull_request_id': pull_request.pull_request_id,
94 'pull_request_id': pull_request.pull_request_id,
95 'comment_id': comments[-1].comment_id,
95 'comment_id': comments[-1].comment_id,
96 'status': {'given': None, 'was_changed': None}
96 'status': {'given': None, 'was_changed': None}
97 }
97 }
98 assert_ok(id_, expected, response.body)
98 assert_ok(id_, expected, response.body)
99 # check user1/user2 inbox for notification
99 # check user1/user2 inbox for notification
100 user1 = User.get(user1_id)
100 user1 = User.get(user1_id)
101 assert 1 == len(user1.notifications)
101 assert 1 == len(user1.notifications)
102 assert 'test message' in user1.notifications[0].notification.body
102 assert 'test message' in user1.notifications[0].notification.body
103
103
104 user2 = User.get(user2_id)
104 user2 = User.get(user2_id)
105 assert 1 == len(user2.notifications)
105 assert 1 == len(user2.notifications)
106 assert 'test message' in user2.notifications[0].notification.body
106 assert 'test message' in user2.notifications[0].notification.body
107
107
108 @pytest.mark.backends("git", "hg")
108 @pytest.mark.backends("git", "hg")
109 def test_api_comment_pull_request_change_status(
109 def test_api_comment_pull_request_change_status(
110 self, pr_util, no_notifications):
110 self, pr_util, no_notifications):
111 pull_request = pr_util.create_pull_request()
111 pull_request = pr_util.create_pull_request()
112 pull_request_id = pull_request.pull_request_id
112 pull_request_id = pull_request.pull_request_id
113 id_, params = build_data(
113 id_, params = build_data(
114 self.apikey, 'comment_pull_request',
114 self.apikey, 'comment_pull_request',
115 repoid=pull_request.target_repo.repo_name,
115 repoid=pull_request.target_repo.repo_name,
116 pullrequestid=pull_request.pull_request_id,
116 pullrequestid=pull_request.pull_request_id,
117 status='rejected')
117 status='rejected')
118 response = api_call(self.app, params)
118 response = api_call(self.app, params)
119 pull_request = PullRequestModel().get(pull_request_id)
119 pull_request = PullRequestModel().get(pull_request_id)
120
120
121 comments = CommentsModel().get_comments(
121 comments = CommentsModel().get_comments(
122 pull_request.target_repo.repo_id, pull_request=pull_request)
122 pull_request.target_repo.repo_id, pull_request=pull_request)
123 expected = {
123 expected = {
124 'pull_request_id': pull_request.pull_request_id,
124 'pull_request_id': pull_request.pull_request_id,
125 'comment_id': comments[-1].comment_id,
125 'comment_id': comments[-1].comment_id,
126 'status': {'given': 'rejected', 'was_changed': True}
126 'status': {'given': 'rejected', 'was_changed': True}
127 }
127 }
128 assert_ok(id_, expected, response.body)
128 assert_ok(id_, expected, response.body)
129
129
130 @pytest.mark.backends("git", "hg")
130 @pytest.mark.backends("git", "hg")
131 def test_api_comment_pull_request_change_status_with_specific_commit_id_and_test_commit(
131 def test_api_comment_pull_request_change_status_with_specific_commit_id_and_test_commit(
132 self, pr_util, no_notifications):
132 self, pr_util, no_notifications):
133 pull_request = pr_util.create_pull_request()
133 pull_request = pr_util.create_pull_request()
134 pull_request_id = pull_request.pull_request_id
134 pull_request_id = pull_request.pull_request_id
135 latest_commit_id = 'test_commit'
135 latest_commit_id = 'test_commit'
136
136
137 # inject additional revision, to fail test the status change on
137 # inject additional revision, to fail test the status change on
138 # non-latest commit
138 # non-latest commit
139 pull_request.revisions = pull_request.revisions + ['test_commit']
139 pull_request.revisions = pull_request.revisions + ['test_commit']
140
140
141 id_, params = build_data(
141 id_, params = build_data(
142 self.apikey, 'comment_pull_request',
142 self.apikey, 'comment_pull_request',
143 message='test-change-of-status-not-allowed',
143 message='test-change-of-status-not-allowed',
144 repoid=pull_request.target_repo.repo_name,
144 repoid=pull_request.target_repo.repo_name,
145 pullrequestid=pull_request.pull_request_id,
145 pullrequestid=pull_request.pull_request_id,
146 status='approved', commit_id=latest_commit_id)
146 status='approved', commit_id=latest_commit_id)
147 response = api_call(self.app, params)
147 response = api_call(self.app, params)
148 pull_request = PullRequestModel().get(pull_request_id)
148 pull_request = PullRequestModel().get(pull_request_id)
149 comments = CommentsModel().get_comments(
149 comments = CommentsModel().get_comments(
150 pull_request.target_repo.repo_id, pull_request=pull_request)
150 pull_request.target_repo.repo_id, pull_request=pull_request)
151
151
152 expected = {
152 expected = {
153 'pull_request_id': pull_request.pull_request_id,
153 'pull_request_id': pull_request.pull_request_id,
154 'comment_id': comments[-1].comment_id,
154 'comment_id': comments[-1].comment_id,
155 'status': {'given': 'approved', 'was_changed': False}
155 'status': {'given': 'approved', 'was_changed': False}
156 }
156 }
157 assert_ok(id_, expected, response.body)
157 assert_ok(id_, expected, response.body)
158
158
159 @pytest.mark.backends("git", "hg")
159 @pytest.mark.backends("git", "hg")
160 def test_api_comment_pull_request_change_status_with_specific_commit_id(
160 def test_api_comment_pull_request_change_status_with_specific_commit_id(
161 self, pr_util, no_notifications):
161 self, pr_util, no_notifications):
162 pull_request = pr_util.create_pull_request()
162 pull_request = pr_util.create_pull_request()
163 pull_request_id = pull_request.pull_request_id
163 pull_request_id = pull_request.pull_request_id
164 latest_commit_id = pull_request.revisions[0]
164 latest_commit_id = pull_request.revisions[0]
165
165
166 id_, params = build_data(
166 id_, params = build_data(
167 self.apikey, 'comment_pull_request',
167 self.apikey, 'comment_pull_request',
168 repoid=pull_request.target_repo.repo_name,
168 repoid=pull_request.target_repo.repo_name,
169 pullrequestid=pull_request.pull_request_id,
169 pullrequestid=pull_request.pull_request_id,
170 status='approved', commit_id=latest_commit_id)
170 status='approved', commit_id=latest_commit_id)
171 response = api_call(self.app, params)
171 response = api_call(self.app, params)
172 pull_request = PullRequestModel().get(pull_request_id)
172 pull_request = PullRequestModel().get(pull_request_id)
173
173
174 comments = CommentsModel().get_comments(
174 comments = CommentsModel().get_comments(
175 pull_request.target_repo.repo_id, pull_request=pull_request)
175 pull_request.target_repo.repo_id, pull_request=pull_request)
176 expected = {
176 expected = {
177 'pull_request_id': pull_request.pull_request_id,
177 'pull_request_id': pull_request.pull_request_id,
178 'comment_id': comments[-1].comment_id,
178 'comment_id': comments[-1].comment_id,
179 'status': {'given': 'approved', 'was_changed': True}
179 'status': {'given': 'approved', 'was_changed': True}
180 }
180 }
181 assert_ok(id_, expected, response.body)
181 assert_ok(id_, expected, response.body)
182
182
183 @pytest.mark.backends("git", "hg")
183 @pytest.mark.backends("git", "hg")
184 def test_api_comment_pull_request_missing_params_error(self, pr_util):
184 def test_api_comment_pull_request_missing_params_error(self, pr_util):
185 pull_request = pr_util.create_pull_request()
185 pull_request = pr_util.create_pull_request()
186 pull_request_id = pull_request.pull_request_id
186 pull_request_id = pull_request.pull_request_id
187 pull_request_repo = pull_request.target_repo.repo_name
187 pull_request_repo = pull_request.target_repo.repo_name
188 id_, params = build_data(
188 id_, params = build_data(
189 self.apikey, 'comment_pull_request',
189 self.apikey, 'comment_pull_request',
190 repoid=pull_request_repo,
190 repoid=pull_request_repo,
191 pullrequestid=pull_request_id)
191 pullrequestid=pull_request_id)
192 response = api_call(self.app, params)
192 response = api_call(self.app, params)
193
193
194 expected = 'Both message and status parameters are missing. At least one is required.'
194 expected = 'Both message and status parameters are missing. At least one is required.'
195 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
196
196
197 @pytest.mark.backends("git", "hg")
197 @pytest.mark.backends("git", "hg")
198 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
198 def test_api_comment_pull_request_unknown_status_error(self, pr_util):
199 pull_request = pr_util.create_pull_request()
199 pull_request = pr_util.create_pull_request()
200 pull_request_id = pull_request.pull_request_id
200 pull_request_id = pull_request.pull_request_id
201 pull_request_repo = pull_request.target_repo.repo_name
201 pull_request_repo = pull_request.target_repo.repo_name
202 id_, params = build_data(
202 id_, params = build_data(
203 self.apikey, 'comment_pull_request',
203 self.apikey, 'comment_pull_request',
204 repoid=pull_request_repo,
204 repoid=pull_request_repo,
205 pullrequestid=pull_request_id,
205 pullrequestid=pull_request_id,
206 status='42')
206 status='42')
207 response = api_call(self.app, params)
207 response = api_call(self.app, params)
208
208
209 expected = 'Unknown comment status: `42`'
209 expected = 'Unknown comment status: `42`'
210 assert_error(id_, expected, given=response.body)
210 assert_error(id_, expected, given=response.body)
211
211
212 @pytest.mark.backends("git", "hg")
212 @pytest.mark.backends("git", "hg")
213 def test_api_comment_pull_request_repo_error(self, pr_util):
213 def test_api_comment_pull_request_repo_error(self, pr_util):
214 pull_request = pr_util.create_pull_request()
214 pull_request = pr_util.create_pull_request()
215 id_, params = build_data(
215 id_, params = build_data(
216 self.apikey, 'comment_pull_request',
216 self.apikey, 'comment_pull_request',
217 repoid=666, pullrequestid=pull_request.pull_request_id)
217 repoid=666, pullrequestid=pull_request.pull_request_id)
218 response = api_call(self.app, params)
218 response = api_call(self.app, params)
219
219
220 expected = 'repository `666` does not exist'
220 expected = 'repository `666` does not exist'
221 assert_error(id_, expected, given=response.body)
221 assert_error(id_, expected, given=response.body)
222
222
223 @pytest.mark.backends("git", "hg")
223 @pytest.mark.backends("git", "hg")
224 def test_api_comment_pull_request_non_admin_with_userid_error(self, pr_util):
224 def test_api_comment_pull_request_non_admin_with_userid_error(self, pr_util):
225 pull_request = pr_util.create_pull_request()
225 pull_request = pr_util.create_pull_request()
226 id_, params = build_data(
226 id_, params = build_data(
227 self.apikey_regular, 'comment_pull_request',
227 self.apikey_regular, 'comment_pull_request',
228 repoid=pull_request.target_repo.repo_name,
228 repoid=pull_request.target_repo.repo_name,
229 pullrequestid=pull_request.pull_request_id,
229 pullrequestid=pull_request.pull_request_id,
230 userid=TEST_USER_ADMIN_LOGIN)
230 userid=TEST_USER_ADMIN_LOGIN)
231 response = api_call(self.app, params)
231 response = api_call(self.app, params)
232
232
233 expected = 'userid is not the same as your user'
233 expected = 'userid is not the same as your user'
234 assert_error(id_, expected, given=response.body)
234 assert_error(id_, expected, given=response.body)
235
235
236 @pytest.mark.backends("git", "hg")
236 @pytest.mark.backends("git", "hg")
237 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
237 def test_api_comment_pull_request_wrong_commit_id_error(self, pr_util):
238 pull_request = pr_util.create_pull_request()
238 pull_request = pr_util.create_pull_request()
239 id_, params = build_data(
239 id_, params = build_data(
240 self.apikey_regular, 'comment_pull_request',
240 self.apikey_regular, 'comment_pull_request',
241 repoid=pull_request.target_repo.repo_name,
241 repoid=pull_request.target_repo.repo_name,
242 status='approved',
242 status='approved',
243 pullrequestid=pull_request.pull_request_id,
243 pullrequestid=pull_request.pull_request_id,
244 commit_id='XXX')
244 commit_id='XXX')
245 response = api_call(self.app, params)
245 response = api_call(self.app, params)
246
246
247 expected = 'Invalid commit_id `XXX` for this pull request.'
247 expected = 'Invalid commit_id `XXX` for this pull request.'
248 assert_error(id_, expected, given=response.body)
248 assert_error(id_, expected, given=response.body)
249
249
250 @pytest.mark.backends("git", "hg")
250 @pytest.mark.backends("git", "hg")
251 def test_api_edit_comment(self, pr_util):
251 def test_api_edit_comment(self, pr_util):
252 pull_request = pr_util.create_pull_request()
252 pull_request = pr_util.create_pull_request()
253
253
254 id_, params = build_data(
254 id_, params = build_data(
255 self.apikey,
255 self.apikey,
256 'comment_pull_request',
256 'comment_pull_request',
257 repoid=pull_request.target_repo.repo_name,
257 repoid=pull_request.target_repo.repo_name,
258 pullrequestid=pull_request.pull_request_id,
258 pullrequestid=pull_request.pull_request_id,
259 message='test message',
259 message='test message',
260 )
260 )
261 response = api_call(self.app, params)
261 response = api_call(self.app, params)
262 json_response = response.json
262 json_response = response.json
263 comment_id = json_response['result']['comment_id']
263 comment_id = json_response['result']['comment_id']
264
264
265 message_after_edit = 'just message'
265 message_after_edit = 'just message'
266 id_, params = build_data(
266 id_, params = build_data(
267 self.apikey,
267 self.apikey,
268 'edit_comment',
268 'edit_comment',
269 comment_id=comment_id,
269 comment_id=comment_id,
270 message=message_after_edit,
270 message=message_after_edit,
271 version=0,
271 version=0,
272 )
272 )
273 response = api_call(self.app, params)
273 response = api_call(self.app, params)
274 json_response = response.json
274 json_response = response.json
275 assert json_response['result']['version'] == 1
275 assert json_response['result']['version'] == 1
276
276
277 text_form_db = ChangesetComment.get(comment_id).text
277 text_form_db = ChangesetComment.get(comment_id).text
278 assert message_after_edit == text_form_db
278 assert message_after_edit == text_form_db
279
279
280 @pytest.mark.backends("git", "hg")
280 @pytest.mark.backends("git", "hg")
281 def test_api_edit_comment_wrong_version_mismatch(self, pr_util):
281 def test_api_edit_comment_wrong_version_mismatch(self, pr_util):
282 pull_request = pr_util.create_pull_request()
282 pull_request = pr_util.create_pull_request()
283
283
284 id_, params = build_data(
284 id_, params = build_data(
285 self.apikey, 'comment_pull_request',
285 self.apikey, 'comment_pull_request',
286 repoid=pull_request.target_repo.repo_name,
286 repoid=pull_request.target_repo.repo_name,
287 pullrequestid=pull_request.pull_request_id,
287 pullrequestid=pull_request.pull_request_id,
288 message='test message')
288 message='test message')
289 response = api_call(self.app, params)
289 response = api_call(self.app, params)
290 json_response = response.json
290 json_response = response.json
291 comment_id = json_response['result']['comment_id']
291 comment_id = json_response['result']['comment_id']
292
292
293 message_after_edit = 'just message'
293 message_after_edit = 'just message'
294 id_, params = build_data(
294 id_, params = build_data(
295 self.apikey,
295 self.apikey,
296 'edit_comment',
296 'edit_comment',
297 comment_id=comment_id,
297 comment_id=comment_id,
298 message=message_after_edit,
298 message=message_after_edit,
299 version=1,
299 version=1,
300 )
300 )
301 response = api_call(self.app, params)
301 response = api_call(self.app, params)
302 expected = 'comment ({}) version ({}) mismatch'.format(comment_id, 1)
302 expected = 'comment ({}) version ({}) mismatch'.format(comment_id, 1)
303 assert_error(id_, expected, given=response.body)
303 assert_error(id_, expected, given=response.body)
304
304
305 @pytest.mark.backends("git", "hg")
305 @pytest.mark.backends("git", "hg")
306 def test_api_edit_comment_wrong_version(self, pr_util):
306 def test_api_edit_comment_wrong_version(self, pr_util):
307 pull_request = pr_util.create_pull_request()
307 pull_request = pr_util.create_pull_request()
308
308
309 id_, params = build_data(
309 id_, params = build_data(
310 self.apikey, 'comment_pull_request',
310 self.apikey, 'comment_pull_request',
311 repoid=pull_request.target_repo.repo_name,
311 repoid=pull_request.target_repo.repo_name,
312 pullrequestid=pull_request.pull_request_id,
312 pullrequestid=pull_request.pull_request_id,
313 message='test message')
313 message='test message')
314 response = api_call(self.app, params)
314 response = api_call(self.app, params)
315 json_response = response.json
315 json_response = response.json
316 comment_id = json_response['result']['comment_id']
316 comment_id = json_response['result']['comment_id']
317
317
318 id_, params = build_data(
318 id_, params = build_data(
319 self.apikey,
319 self.apikey,
320 'edit_comment',
320 'edit_comment',
321 comment_id=comment_id,
321 comment_id=comment_id,
322 message='',
322 message='',
323 version=0,
323 version=0,
324 )
324 )
325 response = api_call(self.app, params)
325 response = api_call(self.app, params)
326 expected = f"comment ({comment_id}) can't be changed with empty string"
326 expected = f"comment ({comment_id}) can't be changed with empty string"
327 assert_error(id_, expected, given=response.body)
327 assert_error(id_, expected, given=response.body)
328
328
329 @pytest.mark.backends("git", "hg")
329 @pytest.mark.backends("git", "hg")
330 def test_api_edit_comment_wrong_user_set_by_non_admin(self, pr_util):
330 def test_api_edit_comment_wrong_user_set_by_non_admin(self, pr_util):
331 pull_request = pr_util.create_pull_request()
331 pull_request = pr_util.create_pull_request()
332 pull_request_id = pull_request.pull_request_id
332 pull_request_id = pull_request.pull_request_id
333 id_, params = build_data(
333 id_, params = build_data(
334 self.apikey,
334 self.apikey,
335 'comment_pull_request',
335 'comment_pull_request',
336 repoid=pull_request.target_repo.repo_name,
336 repoid=pull_request.target_repo.repo_name,
337 pullrequestid=pull_request_id,
337 pullrequestid=pull_request_id,
338 message='test message'
338 message='test message'
339 )
339 )
340 response = api_call(self.app, params)
340 response = api_call(self.app, params)
341 json_response = response.json
341 json_response = response.json
342 comment_id = json_response['result']['comment_id']
342 comment_id = json_response['result']['comment_id']
343
343
344 id_, params = build_data(
344 id_, params = build_data(
345 self.apikey_regular,
345 self.apikey_regular,
346 'edit_comment',
346 'edit_comment',
347 comment_id=comment_id,
347 comment_id=comment_id,
348 message='just message',
348 message='just message',
349 version=0,
349 version=0,
350 userid=TEST_USER_ADMIN_LOGIN
350 userid=TEST_USER_ADMIN_LOGIN
351 )
351 )
352 response = api_call(self.app, params)
352 response = api_call(self.app, params)
353 expected = 'userid is not the same as your user'
353 expected = 'userid is not the same as your user'
354 assert_error(id_, expected, given=response.body)
354 assert_error(id_, expected, given=response.body)
355
355
356 @pytest.mark.backends("git", "hg")
356 @pytest.mark.backends("git", "hg")
357 def test_api_edit_comment_wrong_user_with_permissions_to_edit_comment(self, pr_util):
357 def test_api_edit_comment_wrong_user_with_permissions_to_edit_comment(self, pr_util):
358 pull_request = pr_util.create_pull_request()
358 pull_request = pr_util.create_pull_request()
359 pull_request_id = pull_request.pull_request_id
359 pull_request_id = pull_request.pull_request_id
360 id_, params = build_data(
360 id_, params = build_data(
361 self.apikey,
361 self.apikey,
362 'comment_pull_request',
362 'comment_pull_request',
363 repoid=pull_request.target_repo.repo_name,
363 repoid=pull_request.target_repo.repo_name,
364 pullrequestid=pull_request_id,
364 pullrequestid=pull_request_id,
365 message='test message'
365 message='test message'
366 )
366 )
367 response = api_call(self.app, params)
367 response = api_call(self.app, params)
368 json_response = response.json
368 json_response = response.json
369 comment_id = json_response['result']['comment_id']
369 comment_id = json_response['result']['comment_id']
370
370
371 id_, params = build_data(
371 id_, params = build_data(
372 self.apikey_regular,
372 self.apikey_regular,
373 'edit_comment',
373 'edit_comment',
374 comment_id=comment_id,
374 comment_id=comment_id,
375 message='just message',
375 message='just message',
376 version=0,
376 version=0,
377 )
377 )
378 response = api_call(self.app, params)
378 response = api_call(self.app, params)
379 expected = "you don't have access to edit this comment"
379 expected = "you don't have access to edit this comment"
380 assert_error(id_, expected, given=response.body)
380 assert_error(id_, expected, given=response.body)
@@ -1,101 +1,101 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import Gist
23 from rhodecode.model.db import Gist
24 from rhodecode.model.gist import GistModel
24 from rhodecode.model.gist import GistModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27 from rhodecode.tests.fixture import Fixture
27 from rhodecode.tests.fixture import Fixture
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestApiCreateGist(object):
31 class TestApiCreateGist(object):
32 @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [
32 @pytest.mark.parametrize("lifetime, gist_type, gist_acl_level", [
33 (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC),
33 (10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC),
34 (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE),
34 (20, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PRIVATE),
35 (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC),
35 (40, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PUBLIC),
36 (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE),
36 (80, Gist.GIST_PRIVATE, Gist.ACL_LEVEL_PRIVATE),
37 ])
37 ])
38 def test_api_create_gist(self, lifetime, gist_type, gist_acl_level):
38 def test_api_create_gist(self, lifetime, gist_type, gist_acl_level):
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey_regular, 'create_gist',
40 self.apikey_regular, 'create_gist',
41 lifetime=lifetime,
41 lifetime=lifetime,
42 description='foobar-gist',
42 description='foobar-gist',
43 gist_type=gist_type,
43 gist_type=gist_type,
44 acl_level=gist_acl_level,
44 acl_level=gist_acl_level,
45 files={'foobar_ąć': {'content': 'foo'}})
45 files={'foobar_ąć': {'content': 'foo'}})
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47 response_json = response.json
47 response_json = response.json
48 gist = response_json['result']['gist']
48 gist = response_json['result']['gist']
49 expected = {
49 expected = {
50 'gist': {
50 'gist': {
51 'access_id': gist['access_id'],
51 'access_id': gist['access_id'],
52 'created_on': gist['created_on'],
52 'created_on': gist['created_on'],
53 'modified_at': gist['modified_at'],
53 'modified_at': gist['modified_at'],
54 'description': 'foobar-gist',
54 'description': 'foobar-gist',
55 'expires': gist['expires'],
55 'expires': gist['expires'],
56 'gist_id': gist['gist_id'],
56 'gist_id': gist['gist_id'],
57 'type': gist_type,
57 'type': gist_type,
58 'url': gist['url'],
58 'url': gist['url'],
59 # content is empty since we don't show it here
59 # content is empty since we don't show it here
60 'content': None,
60 'content': None,
61 'acl_level': gist_acl_level,
61 'acl_level': gist_acl_level,
62 },
62 },
63 'msg': 'created new gist'
63 'msg': 'created new gist'
64 }
64 }
65 try:
65 try:
66 assert_ok(id_, expected, given=response.body)
66 assert_ok(id_, expected, given=response.body)
67 finally:
67 finally:
68 Fixture().destroy_gists()
68 Fixture().destroy_gists()
69
69
70 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
70 @pytest.mark.parametrize("expected, lifetime, gist_type, gist_acl_level, files", [
71 ({'gist_type': '"ups" is not one of private, public'},
71 ({'gist_type': '"ups" is not one of private, public'},
72 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
72 10, 'ups', Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
73
73
74 ({'lifetime': '-120 is less than minimum value -1'},
74 ({'lifetime': '-120 is less than minimum value -1'},
75 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
75 -120, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'content': 'f'}}),
76
76
77 ({'0.content': 'Required'},
77 ({'0.content': 'Required'},
78 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
78 10, Gist.GIST_PUBLIC, Gist.ACL_LEVEL_PUBLIC, {'f': {'x': 'f'}}),
79 ])
79 ])
80 def test_api_try_create_gist(
80 def test_api_try_create_gist(
81 self, expected, lifetime, gist_type, gist_acl_level, files):
81 self, expected, lifetime, gist_type, gist_acl_level, files):
82 id_, params = build_data(
82 id_, params = build_data(
83 self.apikey_regular, 'create_gist',
83 self.apikey_regular, 'create_gist',
84 lifetime=lifetime,
84 lifetime=lifetime,
85 description='foobar-gist',
85 description='foobar-gist',
86 gist_type=gist_type,
86 gist_type=gist_type,
87 acl_level=gist_acl_level,
87 acl_level=gist_acl_level,
88 files=files)
88 files=files)
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90
90
91 try:
91 try:
92 assert_error(id_, expected, given=response.body)
92 assert_error(id_, expected, given=response.body)
93 finally:
93 finally:
94 Fixture().destroy_gists()
94 Fixture().destroy_gists()
95
95
96 @mock.patch.object(GistModel, 'create', crash)
96 @mock.patch.object(GistModel, 'create', crash)
97 def test_api_create_gist_exception_occurred(self):
97 def test_api_create_gist_exception_occurred(self):
98 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
98 id_, params = build_data(self.apikey_regular, 'create_gist', files={})
99 response = api_call(self.app, params)
99 response = api_call(self.app, params)
100 expected = 'failed to create gist'
100 expected = 'failed to create gist'
101 assert_error(id_, expected, given=response.body)
101 assert_error(id_, expected, given=response.body)
@@ -1,367 +1,367 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.db import User
22 from rhodecode.model.db import User
23 from rhodecode.model.pull_request import PullRequestModel
23 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
27 from rhodecode.api.tests.utils import build_data, api_call, assert_error
27 from rhodecode.api.tests.utils import build_data, api_call, assert_error
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestCreatePullRequestApi(object):
31 class TestCreatePullRequestApi(object):
32 finalizers = []
32 finalizers = []
33
33
34 def teardown_method(self, method):
34 def teardown_method(self, method):
35 if self.finalizers:
35 if self.finalizers:
36 for finalizer in self.finalizers:
36 for finalizer in self.finalizers:
37 finalizer()
37 finalizer()
38 self.finalizers = []
38 self.finalizers = []
39
39
40 def test_create_with_wrong_data(self):
40 def test_create_with_wrong_data(self):
41 required_data = {
41 required_data = {
42 'source_repo': 'tests/source_repo',
42 'source_repo': 'tests/source_repo',
43 'target_repo': 'tests/target_repo',
43 'target_repo': 'tests/target_repo',
44 'source_ref': 'branch:default:initial',
44 'source_ref': 'branch:default:initial',
45 'target_ref': 'branch:default:new-feature',
45 'target_ref': 'branch:default:new-feature',
46 }
46 }
47 for key in required_data:
47 for key in required_data:
48 data = required_data.copy()
48 data = required_data.copy()
49 data.pop(key)
49 data.pop(key)
50 id_, params = build_data(
50 id_, params = build_data(
51 self.apikey, 'create_pull_request', **data)
51 self.apikey, 'create_pull_request', **data)
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
54 expected = 'Missing non optional `{}` arg in JSON DATA'.format(key)
55 assert_error(id_, expected, given=response.body)
55 assert_error(id_, expected, given=response.body)
56
56
57 @pytest.mark.backends("git", "hg")
57 @pytest.mark.backends("git", "hg")
58 @pytest.mark.parametrize('source_ref', [
58 @pytest.mark.parametrize('source_ref', [
59 'bookmarg:default:initial'
59 'bookmarg:default:initial'
60 ])
60 ])
61 def test_create_with_wrong_refs_data(self, backend, source_ref):
61 def test_create_with_wrong_refs_data(self, backend, source_ref):
62
62
63 data = self._prepare_data(backend)
63 data = self._prepare_data(backend)
64 data['source_ref'] = source_ref
64 data['source_ref'] = source_ref
65
65
66 id_, params = build_data(
66 id_, params = build_data(
67 self.apikey_regular, 'create_pull_request', **data)
67 self.apikey_regular, 'create_pull_request', **data)
68
68
69 response = api_call(self.app, params)
69 response = api_call(self.app, params)
70
70
71 expected = "Ref `{}` type is not allowed. " \
71 expected = "Ref `{}` type is not allowed. " \
72 "Only:['bookmark', 'book', 'tag', 'branch'] " \
72 "Only:['bookmark', 'book', 'tag', 'branch'] " \
73 "are possible.".format(source_ref)
73 "are possible.".format(source_ref)
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
75
75
76 @pytest.mark.backends("git", "hg")
76 @pytest.mark.backends("git", "hg")
77 def test_create_with_correct_data(self, backend):
77 def test_create_with_correct_data(self, backend):
78 data = self._prepare_data(backend)
78 data = self._prepare_data(backend)
79 RepoModel().revoke_user_permission(
79 RepoModel().revoke_user_permission(
80 self.source.repo_name, User.DEFAULT_USER)
80 self.source.repo_name, User.DEFAULT_USER)
81 id_, params = build_data(
81 id_, params = build_data(
82 self.apikey_regular, 'create_pull_request', **data)
82 self.apikey_regular, 'create_pull_request', **data)
83 response = api_call(self.app, params)
83 response = api_call(self.app, params)
84 expected_message = "Created new pull request `{title}`".format(
84 expected_message = "Created new pull request `{title}`".format(
85 title=data['title'])
85 title=data['title'])
86 result = response.json
86 result = response.json
87 assert result['error'] is None
87 assert result['error'] is None
88 assert result['result']['msg'] == expected_message
88 assert result['result']['msg'] == expected_message
89 pull_request_id = result['result']['pull_request_id']
89 pull_request_id = result['result']['pull_request_id']
90 pull_request = PullRequestModel().get(pull_request_id)
90 pull_request = PullRequestModel().get(pull_request_id)
91 assert pull_request.title == data['title']
91 assert pull_request.title == data['title']
92 assert pull_request.description == data['description']
92 assert pull_request.description == data['description']
93 assert pull_request.source_ref == data['source_ref']
93 assert pull_request.source_ref == data['source_ref']
94 assert pull_request.target_ref == data['target_ref']
94 assert pull_request.target_ref == data['target_ref']
95 assert pull_request.source_repo.repo_name == data['source_repo']
95 assert pull_request.source_repo.repo_name == data['source_repo']
96 assert pull_request.target_repo.repo_name == data['target_repo']
96 assert pull_request.target_repo.repo_name == data['target_repo']
97 assert pull_request.revisions == [self.commit_ids['change']]
97 assert pull_request.revisions == [self.commit_ids['change']]
98 assert len(pull_request.reviewers) == 1
98 assert len(pull_request.reviewers) == 1
99
99
100 @pytest.mark.backends("git", "hg")
100 @pytest.mark.backends("git", "hg")
101 def test_create_with_empty_description(self, backend):
101 def test_create_with_empty_description(self, backend):
102 data = self._prepare_data(backend)
102 data = self._prepare_data(backend)
103 data.pop('description')
103 data.pop('description')
104 id_, params = build_data(
104 id_, params = build_data(
105 self.apikey_regular, 'create_pull_request', **data)
105 self.apikey_regular, 'create_pull_request', **data)
106 response = api_call(self.app, params)
106 response = api_call(self.app, params)
107 expected_message = "Created new pull request `{title}`".format(
107 expected_message = "Created new pull request `{title}`".format(
108 title=data['title'])
108 title=data['title'])
109 result = response.json
109 result = response.json
110 assert result['error'] is None
110 assert result['error'] is None
111 assert result['result']['msg'] == expected_message
111 assert result['result']['msg'] == expected_message
112 pull_request_id = result['result']['pull_request_id']
112 pull_request_id = result['result']['pull_request_id']
113 pull_request = PullRequestModel().get(pull_request_id)
113 pull_request = PullRequestModel().get(pull_request_id)
114 assert pull_request.description == ''
114 assert pull_request.description == ''
115
115
116 @pytest.mark.backends("git", "hg")
116 @pytest.mark.backends("git", "hg")
117 def test_create_with_empty_title(self, backend):
117 def test_create_with_empty_title(self, backend):
118 data = self._prepare_data(backend)
118 data = self._prepare_data(backend)
119 data.pop('title')
119 data.pop('title')
120 id_, params = build_data(
120 id_, params = build_data(
121 self.apikey_regular, 'create_pull_request', **data)
121 self.apikey_regular, 'create_pull_request', **data)
122 response = api_call(self.app, params)
122 response = api_call(self.app, params)
123 result = response.json
123 result = response.json
124 pull_request_id = result['result']['pull_request_id']
124 pull_request_id = result['result']['pull_request_id']
125 pull_request = PullRequestModel().get(pull_request_id)
125 pull_request = PullRequestModel().get(pull_request_id)
126 data['ref'] = backend.default_branch_name
126 data['ref'] = backend.default_branch_name
127 title = '{source_repo}#{ref} to {target_repo}'.format(**data)
127 title = '{source_repo}#{ref} to {target_repo}'.format(**data)
128 assert pull_request.title == title
128 assert pull_request.title == title
129
129
130 @pytest.mark.backends("git", "hg")
130 @pytest.mark.backends("git", "hg")
131 def test_create_with_reviewers_specified_by_names(
131 def test_create_with_reviewers_specified_by_names(
132 self, backend, no_notifications):
132 self, backend, no_notifications):
133 data = self._prepare_data(backend)
133 data = self._prepare_data(backend)
134 reviewers = [
134 reviewers = [
135 {'username': TEST_USER_REGULAR_LOGIN,
135 {'username': TEST_USER_REGULAR_LOGIN,
136 'reasons': ['{} added manually'.format(TEST_USER_REGULAR_LOGIN)]},
136 'reasons': ['{} added manually'.format(TEST_USER_REGULAR_LOGIN)]},
137 {'username': TEST_USER_ADMIN_LOGIN,
137 {'username': TEST_USER_ADMIN_LOGIN,
138 'reasons': ['{} added manually'.format(TEST_USER_ADMIN_LOGIN)],
138 'reasons': ['{} added manually'.format(TEST_USER_ADMIN_LOGIN)],
139 'mandatory': True},
139 'mandatory': True},
140 ]
140 ]
141 data['reviewers'] = reviewers
141 data['reviewers'] = reviewers
142
142
143 id_, params = build_data(
143 id_, params = build_data(
144 self.apikey_regular, 'create_pull_request', **data)
144 self.apikey_regular, 'create_pull_request', **data)
145 response = api_call(self.app, params)
145 response = api_call(self.app, params)
146
146
147 expected_message = "Created new pull request `{title}`".format(
147 expected_message = "Created new pull request `{title}`".format(
148 title=data['title'])
148 title=data['title'])
149 result = response.json
149 result = response.json
150 assert result['error'] is None
150 assert result['error'] is None
151 assert result['result']['msg'] == expected_message
151 assert result['result']['msg'] == expected_message
152 pull_request_id = result['result']['pull_request_id']
152 pull_request_id = result['result']['pull_request_id']
153 pull_request = PullRequestModel().get(pull_request_id)
153 pull_request = PullRequestModel().get(pull_request_id)
154
154
155 actual_reviewers = []
155 actual_reviewers = []
156 for rev in pull_request.reviewers:
156 for rev in pull_request.reviewers:
157 entry = {
157 entry = {
158 'username': rev.user.username,
158 'username': rev.user.username,
159 'reasons': rev.reasons,
159 'reasons': rev.reasons,
160 }
160 }
161 if rev.mandatory:
161 if rev.mandatory:
162 entry['mandatory'] = rev.mandatory
162 entry['mandatory'] = rev.mandatory
163 actual_reviewers.append(entry)
163 actual_reviewers.append(entry)
164
164
165 owner_username = pull_request.target_repo.user.username
165 owner_username = pull_request.target_repo.user.username
166 for spec_reviewer in reviewers[::]:
166 for spec_reviewer in reviewers[::]:
167 # default reviewer will be added who is an owner of the repo
167 # default reviewer will be added who is an owner of the repo
168 # this get's overridden by a add owner to reviewers rule
168 # this get's overridden by a add owner to reviewers rule
169 if spec_reviewer['username'] == owner_username:
169 if spec_reviewer['username'] == owner_username:
170 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
170 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
171 # since owner is more important, we don't inherit mandatory flag
171 # since owner is more important, we don't inherit mandatory flag
172 del spec_reviewer['mandatory']
172 del spec_reviewer['mandatory']
173
173
174 assert sorted(actual_reviewers, key=lambda e: e['username']) \
174 assert sorted(actual_reviewers, key=lambda e: e['username']) \
175 == sorted(reviewers, key=lambda e: e['username'])
175 == sorted(reviewers, key=lambda e: e['username'])
176
176
177 @pytest.mark.backends("git", "hg")
177 @pytest.mark.backends("git", "hg")
178 def test_create_with_reviewers_specified_by_ids(
178 def test_create_with_reviewers_specified_by_ids(
179 self, backend, no_notifications):
179 self, backend, no_notifications):
180 data = self._prepare_data(backend)
180 data = self._prepare_data(backend)
181 reviewers = [
181 reviewers = [
182 {'username': UserModel().get_by_username(
182 {'username': UserModel().get_by_username(
183 TEST_USER_REGULAR_LOGIN).user_id,
183 TEST_USER_REGULAR_LOGIN).user_id,
184 'reasons': ['added manually']},
184 'reasons': ['added manually']},
185 {'username': UserModel().get_by_username(
185 {'username': UserModel().get_by_username(
186 TEST_USER_ADMIN_LOGIN).user_id,
186 TEST_USER_ADMIN_LOGIN).user_id,
187 'reasons': ['added manually']},
187 'reasons': ['added manually']},
188 ]
188 ]
189
189
190 data['reviewers'] = reviewers
190 data['reviewers'] = reviewers
191 id_, params = build_data(
191 id_, params = build_data(
192 self.apikey_regular, 'create_pull_request', **data)
192 self.apikey_regular, 'create_pull_request', **data)
193 response = api_call(self.app, params)
193 response = api_call(self.app, params)
194
194
195 expected_message = "Created new pull request `{title}`".format(
195 expected_message = "Created new pull request `{title}`".format(
196 title=data['title'])
196 title=data['title'])
197 result = response.json
197 result = response.json
198 assert result['error'] is None
198 assert result['error'] is None
199 assert result['result']['msg'] == expected_message
199 assert result['result']['msg'] == expected_message
200 pull_request_id = result['result']['pull_request_id']
200 pull_request_id = result['result']['pull_request_id']
201 pull_request = PullRequestModel().get(pull_request_id)
201 pull_request = PullRequestModel().get(pull_request_id)
202
202
203 actual_reviewers = []
203 actual_reviewers = []
204 for rev in pull_request.reviewers:
204 for rev in pull_request.reviewers:
205 entry = {
205 entry = {
206 'username': rev.user.user_id,
206 'username': rev.user.user_id,
207 'reasons': rev.reasons,
207 'reasons': rev.reasons,
208 }
208 }
209 if rev.mandatory:
209 if rev.mandatory:
210 entry['mandatory'] = rev.mandatory
210 entry['mandatory'] = rev.mandatory
211 actual_reviewers.append(entry)
211 actual_reviewers.append(entry)
212
212
213 owner_user_id = pull_request.target_repo.user.user_id
213 owner_user_id = pull_request.target_repo.user.user_id
214 for spec_reviewer in reviewers[::]:
214 for spec_reviewer in reviewers[::]:
215 # default reviewer will be added who is an owner of the repo
215 # default reviewer will be added who is an owner of the repo
216 # this get's overridden by a add owner to reviewers rule
216 # this get's overridden by a add owner to reviewers rule
217 if spec_reviewer['username'] == owner_user_id:
217 if spec_reviewer['username'] == owner_user_id:
218 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
218 spec_reviewer['reasons'] = [u'Default reviewer', u'Repository owner']
219
219
220 assert sorted(actual_reviewers, key=lambda e: e['username']) \
220 assert sorted(actual_reviewers, key=lambda e: e['username']) \
221 == sorted(reviewers, key=lambda e: e['username'])
221 == sorted(reviewers, key=lambda e: e['username'])
222
222
223 @pytest.mark.backends("git", "hg")
223 @pytest.mark.backends("git", "hg")
224 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
224 def test_create_fails_when_the_reviewer_is_not_found(self, backend):
225 data = self._prepare_data(backend)
225 data = self._prepare_data(backend)
226 data['reviewers'] = [{'username': 'somebody'}]
226 data['reviewers'] = [{'username': 'somebody'}]
227 id_, params = build_data(
227 id_, params = build_data(
228 self.apikey_regular, 'create_pull_request', **data)
228 self.apikey_regular, 'create_pull_request', **data)
229 response = api_call(self.app, params)
229 response = api_call(self.app, params)
230 expected_message = 'user `somebody` does not exist'
230 expected_message = 'user `somebody` does not exist'
231 assert_error(id_, expected_message, given=response.body)
231 assert_error(id_, expected_message, given=response.body)
232
232
233 @pytest.mark.backends("git", "hg")
233 @pytest.mark.backends("git", "hg")
234 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
234 def test_cannot_create_with_reviewers_in_wrong_format(self, backend):
235 data = self._prepare_data(backend)
235 data = self._prepare_data(backend)
236 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
236 reviewers = ','.join([TEST_USER_REGULAR_LOGIN, TEST_USER_ADMIN_LOGIN])
237 data['reviewers'] = reviewers
237 data['reviewers'] = reviewers
238 id_, params = build_data(
238 id_, params = build_data(
239 self.apikey_regular, 'create_pull_request', **data)
239 self.apikey_regular, 'create_pull_request', **data)
240 response = api_call(self.app, params)
240 response = api_call(self.app, params)
241 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
241 expected_message = {u'': '"test_regular,test_admin" is not iterable'}
242 assert_error(id_, expected_message, given=response.body)
242 assert_error(id_, expected_message, given=response.body)
243
243
244 @pytest.mark.backends("git", "hg")
244 @pytest.mark.backends("git", "hg")
245 def test_create_with_no_commit_hashes(self, backend):
245 def test_create_with_no_commit_hashes(self, backend):
246 data = self._prepare_data(backend)
246 data = self._prepare_data(backend)
247 expected_source_ref = data['source_ref']
247 expected_source_ref = data['source_ref']
248 expected_target_ref = data['target_ref']
248 expected_target_ref = data['target_ref']
249 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
249 data['source_ref'] = 'branch:{}'.format(backend.default_branch_name)
250 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
250 data['target_ref'] = 'branch:{}'.format(backend.default_branch_name)
251 id_, params = build_data(
251 id_, params = build_data(
252 self.apikey_regular, 'create_pull_request', **data)
252 self.apikey_regular, 'create_pull_request', **data)
253 response = api_call(self.app, params)
253 response = api_call(self.app, params)
254 expected_message = "Created new pull request `{title}`".format(
254 expected_message = "Created new pull request `{title}`".format(
255 title=data['title'])
255 title=data['title'])
256 result = response.json
256 result = response.json
257 assert result['result']['msg'] == expected_message
257 assert result['result']['msg'] == expected_message
258 pull_request_id = result['result']['pull_request_id']
258 pull_request_id = result['result']['pull_request_id']
259 pull_request = PullRequestModel().get(pull_request_id)
259 pull_request = PullRequestModel().get(pull_request_id)
260 assert pull_request.source_ref == expected_source_ref
260 assert pull_request.source_ref == expected_source_ref
261 assert pull_request.target_ref == expected_target_ref
261 assert pull_request.target_ref == expected_target_ref
262
262
263 @pytest.mark.backends("git", "hg")
263 @pytest.mark.backends("git", "hg")
264 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
264 @pytest.mark.parametrize("data_key", ["source_repo", "target_repo"])
265 def test_create_fails_with_wrong_repo(self, backend, data_key):
265 def test_create_fails_with_wrong_repo(self, backend, data_key):
266 repo_name = 'fake-repo'
266 repo_name = 'fake-repo'
267 data = self._prepare_data(backend)
267 data = self._prepare_data(backend)
268 data[data_key] = repo_name
268 data[data_key] = repo_name
269 id_, params = build_data(
269 id_, params = build_data(
270 self.apikey_regular, 'create_pull_request', **data)
270 self.apikey_regular, 'create_pull_request', **data)
271 response = api_call(self.app, params)
271 response = api_call(self.app, params)
272 expected_message = 'repository `{}` does not exist'.format(repo_name)
272 expected_message = 'repository `{}` does not exist'.format(repo_name)
273 assert_error(id_, expected_message, given=response.body)
273 assert_error(id_, expected_message, given=response.body)
274
274
275 @pytest.mark.backends("git", "hg")
275 @pytest.mark.backends("git", "hg")
276 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
276 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
277 def test_create_fails_with_non_existing_branch(self, backend, data_key):
277 def test_create_fails_with_non_existing_branch(self, backend, data_key):
278 branch_name = 'test-branch'
278 branch_name = 'test-branch'
279 data = self._prepare_data(backend)
279 data = self._prepare_data(backend)
280 data[data_key] = "branch:{}".format(branch_name)
280 data[data_key] = "branch:{}".format(branch_name)
281 id_, params = build_data(
281 id_, params = build_data(
282 self.apikey_regular, 'create_pull_request', **data)
282 self.apikey_regular, 'create_pull_request', **data)
283 response = api_call(self.app, params)
283 response = api_call(self.app, params)
284 expected_message = 'The specified value:{type}:`{name}` ' \
284 expected_message = 'The specified value:{type}:`{name}` ' \
285 'does not exist, or is not allowed.'.format(type='branch',
285 'does not exist, or is not allowed.'.format(type='branch',
286 name=branch_name)
286 name=branch_name)
287 assert_error(id_, expected_message, given=response.body)
287 assert_error(id_, expected_message, given=response.body)
288
288
289 @pytest.mark.backends("git", "hg")
289 @pytest.mark.backends("git", "hg")
290 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
290 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
291 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
291 def test_create_fails_with_ref_in_a_wrong_format(self, backend, data_key):
292 data = self._prepare_data(backend)
292 data = self._prepare_data(backend)
293 ref = 'stange-ref'
293 ref = 'stange-ref'
294 data[data_key] = ref
294 data[data_key] = ref
295 id_, params = build_data(
295 id_, params = build_data(
296 self.apikey_regular, 'create_pull_request', **data)
296 self.apikey_regular, 'create_pull_request', **data)
297 response = api_call(self.app, params)
297 response = api_call(self.app, params)
298 expected_message = (
298 expected_message = (
299 'Ref `{ref}` given in a wrong format. Please check the API'
299 'Ref `{ref}` given in a wrong format. Please check the API'
300 ' documentation for more details'.format(ref=ref))
300 ' documentation for more details'.format(ref=ref))
301 assert_error(id_, expected_message, given=response.body)
301 assert_error(id_, expected_message, given=response.body)
302
302
303 @pytest.mark.backends("git", "hg")
303 @pytest.mark.backends("git", "hg")
304 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
304 @pytest.mark.parametrize("data_key", ["source_ref", "target_ref"])
305 def test_create_fails_with_non_existing_ref(self, backend, data_key):
305 def test_create_fails_with_non_existing_ref(self, backend, data_key):
306 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
306 commit_id = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
307 ref = self._get_full_ref(backend, commit_id)
307 ref = self._get_full_ref(backend, commit_id)
308 data = self._prepare_data(backend)
308 data = self._prepare_data(backend)
309 data[data_key] = ref
309 data[data_key] = ref
310 id_, params = build_data(
310 id_, params = build_data(
311 self.apikey_regular, 'create_pull_request', **data)
311 self.apikey_regular, 'create_pull_request', **data)
312 response = api_call(self.app, params)
312 response = api_call(self.app, params)
313 expected_message = 'Ref `{}` does not exist'.format(ref)
313 expected_message = 'Ref `{}` does not exist'.format(ref)
314 assert_error(id_, expected_message, given=response.body)
314 assert_error(id_, expected_message, given=response.body)
315
315
316 @pytest.mark.backends("git", "hg")
316 @pytest.mark.backends("git", "hg")
317 def test_create_fails_when_no_revisions(self, backend):
317 def test_create_fails_when_no_revisions(self, backend):
318 data = self._prepare_data(backend, source_head='initial')
318 data = self._prepare_data(backend, source_head='initial')
319 id_, params = build_data(
319 id_, params = build_data(
320 self.apikey_regular, 'create_pull_request', **data)
320 self.apikey_regular, 'create_pull_request', **data)
321 response = api_call(self.app, params)
321 response = api_call(self.app, params)
322 expected_message = 'no commits found for merge between specified references'
322 expected_message = 'no commits found for merge between specified references'
323 assert_error(id_, expected_message, given=response.body)
323 assert_error(id_, expected_message, given=response.body)
324
324
325 @pytest.mark.backends("git", "hg")
325 @pytest.mark.backends("git", "hg")
326 def test_create_fails_when_no_permissions(self, backend):
326 def test_create_fails_when_no_permissions(self, backend):
327 data = self._prepare_data(backend)
327 data = self._prepare_data(backend)
328 RepoModel().revoke_user_permission(
328 RepoModel().revoke_user_permission(
329 self.source.repo_name, self.test_user)
329 self.source.repo_name, self.test_user)
330 RepoModel().revoke_user_permission(
330 RepoModel().revoke_user_permission(
331 self.source.repo_name, User.DEFAULT_USER)
331 self.source.repo_name, User.DEFAULT_USER)
332
332
333 id_, params = build_data(
333 id_, params = build_data(
334 self.apikey_regular, 'create_pull_request', **data)
334 self.apikey_regular, 'create_pull_request', **data)
335 response = api_call(self.app, params)
335 response = api_call(self.app, params)
336 expected_message = 'repository `{}` does not exist'.format(
336 expected_message = 'repository `{}` does not exist'.format(
337 self.source.repo_name)
337 self.source.repo_name)
338 assert_error(id_, expected_message, given=response.body)
338 assert_error(id_, expected_message, given=response.body)
339
339
340 def _prepare_data(
340 def _prepare_data(
341 self, backend, source_head='change', target_head='initial'):
341 self, backend, source_head='change', target_head='initial'):
342 commits = [
342 commits = [
343 {'message': 'initial'},
343 {'message': 'initial'},
344 {'message': 'change'},
344 {'message': 'change'},
345 {'message': 'new-feature', 'parents': ['initial']},
345 {'message': 'new-feature', 'parents': ['initial']},
346 ]
346 ]
347 self.commit_ids = backend.create_master_repo(commits)
347 self.commit_ids = backend.create_master_repo(commits)
348 self.source = backend.create_repo(heads=[source_head])
348 self.source = backend.create_repo(heads=[source_head])
349 self.target = backend.create_repo(heads=[target_head])
349 self.target = backend.create_repo(heads=[target_head])
350
350
351 data = {
351 data = {
352 'source_repo': self.source.repo_name,
352 'source_repo': self.source.repo_name,
353 'target_repo': self.target.repo_name,
353 'target_repo': self.target.repo_name,
354 'source_ref': self._get_full_ref(
354 'source_ref': self._get_full_ref(
355 backend, self.commit_ids[source_head]),
355 backend, self.commit_ids[source_head]),
356 'target_ref': self._get_full_ref(
356 'target_ref': self._get_full_ref(
357 backend, self.commit_ids[target_head]),
357 backend, self.commit_ids[target_head]),
358 'title': 'Test PR 1',
358 'title': 'Test PR 1',
359 'description': 'Test'
359 'description': 'Test'
360 }
360 }
361 RepoModel().grant_user_permission(
361 RepoModel().grant_user_permission(
362 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
362 self.source.repo_name, self.TEST_USER_LOGIN, 'repository.read')
363 return data
363 return data
364
364
365 def _get_full_ref(self, backend, commit_id):
365 def _get_full_ref(self, backend, commit_id):
366 return 'branch:{branch}:{commit_id}'.format(
366 return 'branch:{branch}:{commit_id}'.format(
367 branch=backend.default_branch_name, commit_id=commit_id)
367 branch=backend.default_branch_name, commit_id=commit_id)
@@ -1,348 +1,348 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.vcs import settings
23 from rhodecode.lib.vcs import settings
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.user import UserModel
26 from rhodecode.model.user import UserModel
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, crash)
29 build_data, api_call, assert_ok, assert_error, crash)
30 from rhodecode.tests.fixture import Fixture
30 from rhodecode.tests.fixture import Fixture
31 from rhodecode.lib.ext_json import json
31 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.str_utils import safe_str
32 from rhodecode.lib.str_utils import safe_str
33
33
34
34
35 fixture = Fixture()
35 fixture = Fixture()
36
36
37
37
38 @pytest.mark.usefixtures("testuser_api", "app")
38 @pytest.mark.usefixtures("testuser_api", "app")
39 class TestCreateRepo(object):
39 class TestCreateRepo(object):
40
40
41 @pytest.mark.parametrize('given, expected_name, expected_exc', [
41 @pytest.mark.parametrize('given, expected_name, expected_exc', [
42 ('api repo-1', 'api-repo-1', False),
42 ('api repo-1', 'api-repo-1', False),
43 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
43 ('api-repo 1-ąć', 'api-repo-1-ąć', False),
44 (u'unicode-ąć', u'unicode-ąć', False),
44 (u'unicode-ąć', u'unicode-ąć', False),
45 ('some repo v1.2', 'some-repo-v1.2', False),
45 ('some repo v1.2', 'some-repo-v1.2', False),
46 ('v2.0', 'v2.0', False),
46 ('v2.0', 'v2.0', False),
47 ])
47 ])
48 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
48 def test_api_create_repo(self, backend, given, expected_name, expected_exc):
49
49
50 id_, params = build_data(
50 id_, params = build_data(
51 self.apikey,
51 self.apikey,
52 'create_repo',
52 'create_repo',
53 repo_name=given,
53 repo_name=given,
54 owner=TEST_USER_ADMIN_LOGIN,
54 owner=TEST_USER_ADMIN_LOGIN,
55 repo_type=backend.alias,
55 repo_type=backend.alias,
56 )
56 )
57 response = api_call(self.app, params)
57 response = api_call(self.app, params)
58
58
59 ret = {
59 ret = {
60 'msg': 'Created new repository `%s`' % (expected_name,),
60 'msg': 'Created new repository `%s`' % (expected_name,),
61 'success': True,
61 'success': True,
62 'task': None,
62 'task': None,
63 }
63 }
64 expected = ret
64 expected = ret
65 assert_ok(id_, expected, given=response.body)
65 assert_ok(id_, expected, given=response.body)
66
66
67 repo = RepoModel().get_by_repo_name(safe_str(expected_name))
67 repo = RepoModel().get_by_repo_name(safe_str(expected_name))
68 assert repo is not None
68 assert repo is not None
69
69
70 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
70 id_, params = build_data(self.apikey, 'get_repo', repoid=expected_name)
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72 body = json.loads(response.body)
72 body = json.loads(response.body)
73
73
74 assert body['result']['enable_downloads'] is False
74 assert body['result']['enable_downloads'] is False
75 assert body['result']['enable_locking'] is False
75 assert body['result']['enable_locking'] is False
76 assert body['result']['enable_statistics'] is False
76 assert body['result']['enable_statistics'] is False
77
77
78 fixture.destroy_repo(safe_str(expected_name))
78 fixture.destroy_repo(safe_str(expected_name))
79
79
80 def test_api_create_restricted_repo_type(self, backend):
80 def test_api_create_restricted_repo_type(self, backend):
81 repo_name = 'api-repo-type-{0}'.format(backend.alias)
81 repo_name = 'api-repo-type-{0}'.format(backend.alias)
82 id_, params = build_data(
82 id_, params = build_data(
83 self.apikey,
83 self.apikey,
84 'create_repo',
84 'create_repo',
85 repo_name=repo_name,
85 repo_name=repo_name,
86 owner=TEST_USER_ADMIN_LOGIN,
86 owner=TEST_USER_ADMIN_LOGIN,
87 repo_type=backend.alias,
87 repo_type=backend.alias,
88 )
88 )
89 git_backend = settings.BACKENDS['git']
89 git_backend = settings.BACKENDS['git']
90 with mock.patch(
90 with mock.patch(
91 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
91 'rhodecode.lib.vcs.settings.BACKENDS', {'git': git_backend}):
92 response = api_call(self.app, params)
92 response = api_call(self.app, params)
93
93
94 repo = RepoModel().get_by_repo_name(repo_name)
94 repo = RepoModel().get_by_repo_name(repo_name)
95
95
96 if backend.alias == 'git':
96 if backend.alias == 'git':
97 assert repo is not None
97 assert repo is not None
98 expected = {
98 expected = {
99 'msg': 'Created new repository `{0}`'.format(repo_name,),
99 'msg': 'Created new repository `{0}`'.format(repo_name,),
100 'success': True,
100 'success': True,
101 'task': None,
101 'task': None,
102 }
102 }
103 assert_ok(id_, expected, given=response.body)
103 assert_ok(id_, expected, given=response.body)
104 else:
104 else:
105 assert repo is None
105 assert repo is None
106
106
107 fixture.destroy_repo(repo_name)
107 fixture.destroy_repo(repo_name)
108
108
109 def test_api_create_repo_with_booleans(self, backend):
109 def test_api_create_repo_with_booleans(self, backend):
110 repo_name = 'api-repo-2'
110 repo_name = 'api-repo-2'
111 id_, params = build_data(
111 id_, params = build_data(
112 self.apikey,
112 self.apikey,
113 'create_repo',
113 'create_repo',
114 repo_name=repo_name,
114 repo_name=repo_name,
115 owner=TEST_USER_ADMIN_LOGIN,
115 owner=TEST_USER_ADMIN_LOGIN,
116 repo_type=backend.alias,
116 repo_type=backend.alias,
117 enable_statistics=True,
117 enable_statistics=True,
118 enable_locking=True,
118 enable_locking=True,
119 enable_downloads=True
119 enable_downloads=True
120 )
120 )
121 response = api_call(self.app, params)
121 response = api_call(self.app, params)
122
122
123 repo = RepoModel().get_by_repo_name(repo_name)
123 repo = RepoModel().get_by_repo_name(repo_name)
124
124
125 assert repo is not None
125 assert repo is not None
126 ret = {
126 ret = {
127 'msg': 'Created new repository `%s`' % (repo_name,),
127 'msg': 'Created new repository `%s`' % (repo_name,),
128 'success': True,
128 'success': True,
129 'task': None,
129 'task': None,
130 }
130 }
131 expected = ret
131 expected = ret
132 assert_ok(id_, expected, given=response.body)
132 assert_ok(id_, expected, given=response.body)
133
133
134 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
134 id_, params = build_data(self.apikey, 'get_repo', repoid=repo_name)
135 response = api_call(self.app, params)
135 response = api_call(self.app, params)
136 body = json.loads(response.body)
136 body = json.loads(response.body)
137
137
138 assert body['result']['enable_downloads'] is True
138 assert body['result']['enable_downloads'] is True
139 assert body['result']['enable_locking'] is True
139 assert body['result']['enable_locking'] is True
140 assert body['result']['enable_statistics'] is True
140 assert body['result']['enable_statistics'] is True
141
141
142 fixture.destroy_repo(repo_name)
142 fixture.destroy_repo(repo_name)
143
143
144 def test_api_create_repo_in_group(self, backend):
144 def test_api_create_repo_in_group(self, backend):
145 repo_group_name = 'my_gr'
145 repo_group_name = 'my_gr'
146 # create the parent
146 # create the parent
147 fixture.create_repo_group(repo_group_name)
147 fixture.create_repo_group(repo_group_name)
148
148
149 repo_name = '%s/api-repo-gr' % (repo_group_name,)
149 repo_name = '%s/api-repo-gr' % (repo_group_name,)
150 id_, params = build_data(
150 id_, params = build_data(
151 self.apikey, 'create_repo',
151 self.apikey, 'create_repo',
152 repo_name=repo_name,
152 repo_name=repo_name,
153 owner=TEST_USER_ADMIN_LOGIN,
153 owner=TEST_USER_ADMIN_LOGIN,
154 repo_type=backend.alias,)
154 repo_type=backend.alias,)
155 response = api_call(self.app, params)
155 response = api_call(self.app, params)
156 repo = RepoModel().get_by_repo_name(repo_name)
156 repo = RepoModel().get_by_repo_name(repo_name)
157 assert repo is not None
157 assert repo is not None
158 assert repo.group is not None
158 assert repo.group is not None
159
159
160 ret = {
160 ret = {
161 'msg': 'Created new repository `%s`' % (repo_name,),
161 'msg': 'Created new repository `%s`' % (repo_name,),
162 'success': True,
162 'success': True,
163 'task': None,
163 'task': None,
164 }
164 }
165 expected = ret
165 expected = ret
166 assert_ok(id_, expected, given=response.body)
166 assert_ok(id_, expected, given=response.body)
167 fixture.destroy_repo(repo_name)
167 fixture.destroy_repo(repo_name)
168 fixture.destroy_repo_group(repo_group_name)
168 fixture.destroy_repo_group(repo_group_name)
169
169
170 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
170 def test_create_repo_in_group_that_doesnt_exist(self, backend, user_util):
171 repo_group_name = 'fake_group'
171 repo_group_name = 'fake_group'
172
172
173 repo_name = '%s/api-repo-gr' % (repo_group_name,)
173 repo_name = '%s/api-repo-gr' % (repo_group_name,)
174 id_, params = build_data(
174 id_, params = build_data(
175 self.apikey, 'create_repo',
175 self.apikey, 'create_repo',
176 repo_name=repo_name,
176 repo_name=repo_name,
177 owner=TEST_USER_ADMIN_LOGIN,
177 owner=TEST_USER_ADMIN_LOGIN,
178 repo_type=backend.alias,)
178 repo_type=backend.alias,)
179 response = api_call(self.app, params)
179 response = api_call(self.app, params)
180
180
181 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
181 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
182 repo_group_name)}
182 repo_group_name)}
183 assert_error(id_, expected, given=response.body)
183 assert_error(id_, expected, given=response.body)
184
184
185 def test_api_create_repo_unknown_owner(self, backend):
185 def test_api_create_repo_unknown_owner(self, backend):
186 repo_name = 'api-repo-2'
186 repo_name = 'api-repo-2'
187 owner = 'i-dont-exist'
187 owner = 'i-dont-exist'
188 id_, params = build_data(
188 id_, params = build_data(
189 self.apikey, 'create_repo',
189 self.apikey, 'create_repo',
190 repo_name=repo_name,
190 repo_name=repo_name,
191 owner=owner,
191 owner=owner,
192 repo_type=backend.alias)
192 repo_type=backend.alias)
193 response = api_call(self.app, params)
193 response = api_call(self.app, params)
194 expected = 'user `%s` does not exist' % (owner,)
194 expected = 'user `%s` does not exist' % (owner,)
195 assert_error(id_, expected, given=response.body)
195 assert_error(id_, expected, given=response.body)
196
196
197 def test_api_create_repo_dont_specify_owner(self, backend):
197 def test_api_create_repo_dont_specify_owner(self, backend):
198 repo_name = 'api-repo-3'
198 repo_name = 'api-repo-3'
199 id_, params = build_data(
199 id_, params = build_data(
200 self.apikey, 'create_repo',
200 self.apikey, 'create_repo',
201 repo_name=repo_name,
201 repo_name=repo_name,
202 repo_type=backend.alias)
202 repo_type=backend.alias)
203 response = api_call(self.app, params)
203 response = api_call(self.app, params)
204
204
205 repo = RepoModel().get_by_repo_name(repo_name)
205 repo = RepoModel().get_by_repo_name(repo_name)
206 assert repo is not None
206 assert repo is not None
207 ret = {
207 ret = {
208 'msg': 'Created new repository `%s`' % (repo_name,),
208 'msg': 'Created new repository `%s`' % (repo_name,),
209 'success': True,
209 'success': True,
210 'task': None,
210 'task': None,
211 }
211 }
212 expected = ret
212 expected = ret
213 assert_ok(id_, expected, given=response.body)
213 assert_ok(id_, expected, given=response.body)
214 fixture.destroy_repo(repo_name)
214 fixture.destroy_repo(repo_name)
215
215
216 def test_api_create_repo_by_non_admin(self, backend):
216 def test_api_create_repo_by_non_admin(self, backend):
217 repo_name = 'api-repo-4'
217 repo_name = 'api-repo-4'
218 id_, params = build_data(
218 id_, params = build_data(
219 self.apikey_regular, 'create_repo',
219 self.apikey_regular, 'create_repo',
220 repo_name=repo_name,
220 repo_name=repo_name,
221 repo_type=backend.alias)
221 repo_type=backend.alias)
222 response = api_call(self.app, params)
222 response = api_call(self.app, params)
223
223
224 repo = RepoModel().get_by_repo_name(repo_name)
224 repo = RepoModel().get_by_repo_name(repo_name)
225 assert repo is not None
225 assert repo is not None
226 ret = {
226 ret = {
227 'msg': 'Created new repository `%s`' % (repo_name,),
227 'msg': 'Created new repository `%s`' % (repo_name,),
228 'success': True,
228 'success': True,
229 'task': None,
229 'task': None,
230 }
230 }
231 expected = ret
231 expected = ret
232 assert_ok(id_, expected, given=response.body)
232 assert_ok(id_, expected, given=response.body)
233 fixture.destroy_repo(repo_name)
233 fixture.destroy_repo(repo_name)
234
234
235 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
235 def test_api_create_repo_by_non_admin_specify_owner(self, backend):
236 repo_name = 'api-repo-5'
236 repo_name = 'api-repo-5'
237 owner = 'i-dont-exist'
237 owner = 'i-dont-exist'
238 id_, params = build_data(
238 id_, params = build_data(
239 self.apikey_regular, 'create_repo',
239 self.apikey_regular, 'create_repo',
240 repo_name=repo_name,
240 repo_name=repo_name,
241 repo_type=backend.alias,
241 repo_type=backend.alias,
242 owner=owner)
242 owner=owner)
243 response = api_call(self.app, params)
243 response = api_call(self.app, params)
244
244
245 expected = 'Only RhodeCode super-admin can specify `owner` param'
245 expected = 'Only RhodeCode super-admin can specify `owner` param'
246 assert_error(id_, expected, given=response.body)
246 assert_error(id_, expected, given=response.body)
247 fixture.destroy_repo(repo_name)
247 fixture.destroy_repo(repo_name)
248
248
249 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
249 def test_api_create_repo_by_non_admin_no_parent_group_perms(self, backend):
250 repo_group_name = 'no-access'
250 repo_group_name = 'no-access'
251 fixture.create_repo_group(repo_group_name)
251 fixture.create_repo_group(repo_group_name)
252 repo_name = 'no-access/api-repo'
252 repo_name = 'no-access/api-repo'
253
253
254 id_, params = build_data(
254 id_, params = build_data(
255 self.apikey_regular, 'create_repo',
255 self.apikey_regular, 'create_repo',
256 repo_name=repo_name,
256 repo_name=repo_name,
257 repo_type=backend.alias)
257 repo_type=backend.alias)
258 response = api_call(self.app, params)
258 response = api_call(self.app, params)
259
259
260 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
260 expected = {'repo_group': 'Repository group `{}` does not exist'.format(
261 repo_group_name)}
261 repo_group_name)}
262 assert_error(id_, expected, given=response.body)
262 assert_error(id_, expected, given=response.body)
263 fixture.destroy_repo_group(repo_group_name)
263 fixture.destroy_repo_group(repo_group_name)
264 fixture.destroy_repo(repo_name)
264 fixture.destroy_repo(repo_name)
265
265
266 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
266 def test_api_create_repo_non_admin_no_permission_to_create_to_root_level(
267 self, backend, user_util):
267 self, backend, user_util):
268
268
269 regular_user = user_util.create_user()
269 regular_user = user_util.create_user()
270 regular_user_api_key = regular_user.api_key
270 regular_user_api_key = regular_user.api_key
271
271
272 usr = UserModel().get_by_username(regular_user.username)
272 usr = UserModel().get_by_username(regular_user.username)
273 usr.inherit_default_permissions = False
273 usr.inherit_default_permissions = False
274 Session().add(usr)
274 Session().add(usr)
275
275
276 repo_name = backend.new_repo_name()
276 repo_name = backend.new_repo_name()
277 id_, params = build_data(
277 id_, params = build_data(
278 regular_user_api_key, 'create_repo',
278 regular_user_api_key, 'create_repo',
279 repo_name=repo_name,
279 repo_name=repo_name,
280 repo_type=backend.alias)
280 repo_type=backend.alias)
281 response = api_call(self.app, params)
281 response = api_call(self.app, params)
282 expected = {
282 expected = {
283 "repo_name": "You do not have the permission to "
283 "repo_name": "You do not have the permission to "
284 "store repositories in the root location."}
284 "store repositories in the root location."}
285 assert_error(id_, expected, given=response.body)
285 assert_error(id_, expected, given=response.body)
286
286
287 def test_api_create_repo_exists(self, backend):
287 def test_api_create_repo_exists(self, backend):
288 repo_name = backend.repo_name
288 repo_name = backend.repo_name
289 id_, params = build_data(
289 id_, params = build_data(
290 self.apikey, 'create_repo',
290 self.apikey, 'create_repo',
291 repo_name=repo_name,
291 repo_name=repo_name,
292 owner=TEST_USER_ADMIN_LOGIN,
292 owner=TEST_USER_ADMIN_LOGIN,
293 repo_type=backend.alias,)
293 repo_type=backend.alias,)
294 response = api_call(self.app, params)
294 response = api_call(self.app, params)
295 expected = {
295 expected = {
296 'unique_repo_name': 'Repository with name `{}` already exists'.format(
296 'unique_repo_name': 'Repository with name `{}` already exists'.format(
297 repo_name)}
297 repo_name)}
298 assert_error(id_, expected, given=response.body)
298 assert_error(id_, expected, given=response.body)
299
299
300 @mock.patch.object(RepoModel, 'create', crash)
300 @mock.patch.object(RepoModel, 'create', crash)
301 def test_api_create_repo_exception_occurred(self, backend):
301 def test_api_create_repo_exception_occurred(self, backend):
302 repo_name = 'api-repo-6'
302 repo_name = 'api-repo-6'
303 id_, params = build_data(
303 id_, params = build_data(
304 self.apikey, 'create_repo',
304 self.apikey, 'create_repo',
305 repo_name=repo_name,
305 repo_name=repo_name,
306 owner=TEST_USER_ADMIN_LOGIN,
306 owner=TEST_USER_ADMIN_LOGIN,
307 repo_type=backend.alias,)
307 repo_type=backend.alias,)
308 response = api_call(self.app, params)
308 response = api_call(self.app, params)
309 expected = 'failed to create repository `%s`' % (repo_name,)
309 expected = 'failed to create repository `%s`' % (repo_name,)
310 assert_error(id_, expected, given=response.body)
310 assert_error(id_, expected, given=response.body)
311
311
312 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
312 @pytest.mark.parametrize('parent_group, dirty_name, expected_name', [
313 (None, 'foo bar x', 'foo-bar-x'),
313 (None, 'foo bar x', 'foo-bar-x'),
314 ('foo', '/foo//bar x', 'foo/bar-x'),
314 ('foo', '/foo//bar x', 'foo/bar-x'),
315 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
315 ('foo-bar', 'foo-bar //bar x', 'foo-bar/bar-x'),
316 ])
316 ])
317 def test_create_repo_with_extra_slashes_in_name(
317 def test_create_repo_with_extra_slashes_in_name(
318 self, backend, parent_group, dirty_name, expected_name):
318 self, backend, parent_group, dirty_name, expected_name):
319
319
320 if parent_group:
320 if parent_group:
321 gr = fixture.create_repo_group(parent_group)
321 gr = fixture.create_repo_group(parent_group)
322 assert gr.group_name == parent_group
322 assert gr.group_name == parent_group
323
323
324 id_, params = build_data(
324 id_, params = build_data(
325 self.apikey, 'create_repo',
325 self.apikey, 'create_repo',
326 repo_name=dirty_name,
326 repo_name=dirty_name,
327 repo_type=backend.alias,
327 repo_type=backend.alias,
328 owner=TEST_USER_ADMIN_LOGIN,)
328 owner=TEST_USER_ADMIN_LOGIN,)
329 response = api_call(self.app, params)
329 response = api_call(self.app, params)
330 expected ={
330 expected ={
331 "msg": "Created new repository `{}`".format(expected_name),
331 "msg": "Created new repository `{}`".format(expected_name),
332 "task": None,
332 "task": None,
333 "success": True
333 "success": True
334 }
334 }
335 assert_ok(id_, expected, response.body)
335 assert_ok(id_, expected, response.body)
336
336
337 repo = RepoModel().get_by_repo_name(expected_name)
337 repo = RepoModel().get_by_repo_name(expected_name)
338 assert repo is not None
338 assert repo is not None
339
339
340 expected = {
340 expected = {
341 'msg': 'Created new repository `%s`' % (expected_name,),
341 'msg': 'Created new repository `%s`' % (expected_name,),
342 'success': True,
342 'success': True,
343 'task': None,
343 'task': None,
344 }
344 }
345 assert_ok(id_, expected, given=response.body)
345 assert_ok(id_, expected, given=response.body)
346 fixture.destroy_repo(expected_name)
346 fixture.destroy_repo(expected_name)
347 if parent_group:
347 if parent_group:
348 fixture.destroy_repo_group(parent_group)
348 fixture.destroy_repo_group(parent_group)
@@ -1,288 +1,288 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error, crash)
28 build_data, api_call, assert_ok, assert_error, crash)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30
30
31
31
32 fixture = Fixture()
32 fixture = Fixture()
33
33
34
34
35 @pytest.mark.usefixtures("testuser_api", "app")
35 @pytest.mark.usefixtures("testuser_api", "app")
36 class TestCreateRepoGroup(object):
36 class TestCreateRepoGroup(object):
37 def test_api_create_repo_group(self):
37 def test_api_create_repo_group(self):
38 repo_group_name = 'api-repo-group'
38 repo_group_name = 'api-repo-group'
39
39
40 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
40 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
41 assert repo_group is None
41 assert repo_group is None
42
42
43 id_, params = build_data(
43 id_, params = build_data(
44 self.apikey, 'create_repo_group',
44 self.apikey, 'create_repo_group',
45 group_name=repo_group_name,
45 group_name=repo_group_name,
46 owner=TEST_USER_ADMIN_LOGIN,)
46 owner=TEST_USER_ADMIN_LOGIN,)
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48
48
49 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
49 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
50 assert repo_group is not None
50 assert repo_group is not None
51 ret = {
51 ret = {
52 'msg': 'Created new repo group `%s`' % (repo_group_name,),
52 'msg': 'Created new repo group `%s`' % (repo_group_name,),
53 'repo_group': repo_group.get_api_data()
53 'repo_group': repo_group.get_api_data()
54 }
54 }
55 expected = ret
55 expected = ret
56 try:
56 try:
57 assert_ok(id_, expected, given=response.body)
57 assert_ok(id_, expected, given=response.body)
58 finally:
58 finally:
59 fixture.destroy_repo_group(repo_group_name)
59 fixture.destroy_repo_group(repo_group_name)
60
60
61 def test_api_create_repo_group_in_another_group(self):
61 def test_api_create_repo_group_in_another_group(self):
62 repo_group_name = 'api-repo-group'
62 repo_group_name = 'api-repo-group'
63
63
64 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
64 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
65 assert repo_group is None
65 assert repo_group is None
66 # create the parent
66 # create the parent
67 fixture.create_repo_group(repo_group_name)
67 fixture.create_repo_group(repo_group_name)
68
68
69 full_repo_group_name = repo_group_name+'/'+repo_group_name
69 full_repo_group_name = repo_group_name+'/'+repo_group_name
70 id_, params = build_data(
70 id_, params = build_data(
71 self.apikey, 'create_repo_group',
71 self.apikey, 'create_repo_group',
72 group_name=full_repo_group_name,
72 group_name=full_repo_group_name,
73 owner=TEST_USER_ADMIN_LOGIN,
73 owner=TEST_USER_ADMIN_LOGIN,
74 copy_permissions=True)
74 copy_permissions=True)
75 response = api_call(self.app, params)
75 response = api_call(self.app, params)
76
76
77 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
77 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
78 assert repo_group is not None
78 assert repo_group is not None
79 ret = {
79 ret = {
80 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
80 'msg': 'Created new repo group `%s`' % (full_repo_group_name,),
81 'repo_group': repo_group.get_api_data()
81 'repo_group': repo_group.get_api_data()
82 }
82 }
83 expected = ret
83 expected = ret
84 try:
84 try:
85 assert_ok(id_, expected, given=response.body)
85 assert_ok(id_, expected, given=response.body)
86 finally:
86 finally:
87 fixture.destroy_repo_group(full_repo_group_name)
87 fixture.destroy_repo_group(full_repo_group_name)
88 fixture.destroy_repo_group(repo_group_name)
88 fixture.destroy_repo_group(repo_group_name)
89
89
90 def test_api_create_repo_group_in_another_group_not_existing(self):
90 def test_api_create_repo_group_in_another_group_not_existing(self):
91 repo_group_name = 'api-repo-group-no'
91 repo_group_name = 'api-repo-group-no'
92
92
93 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
93 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
94 assert repo_group is None
94 assert repo_group is None
95
95
96 full_repo_group_name = repo_group_name+'/'+repo_group_name
96 full_repo_group_name = repo_group_name+'/'+repo_group_name
97 id_, params = build_data(
97 id_, params = build_data(
98 self.apikey, 'create_repo_group',
98 self.apikey, 'create_repo_group',
99 group_name=full_repo_group_name,
99 group_name=full_repo_group_name,
100 owner=TEST_USER_ADMIN_LOGIN,
100 owner=TEST_USER_ADMIN_LOGIN,
101 copy_permissions=True)
101 copy_permissions=True)
102 response = api_call(self.app, params)
102 response = api_call(self.app, params)
103 expected = {
103 expected = {
104 'repo_group':
104 'repo_group':
105 'Parent repository group `{}` does not exist'.format(
105 'Parent repository group `{}` does not exist'.format(
106 repo_group_name)}
106 repo_group_name)}
107 assert_error(id_, expected, given=response.body)
107 assert_error(id_, expected, given=response.body)
108
108
109 def test_api_create_repo_group_that_exists(self):
109 def test_api_create_repo_group_that_exists(self):
110 repo_group_name = 'api-repo-group'
110 repo_group_name = 'api-repo-group'
111
111
112 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
112 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
113 assert repo_group is None
113 assert repo_group is None
114
114
115 fixture.create_repo_group(repo_group_name)
115 fixture.create_repo_group(repo_group_name)
116 id_, params = build_data(
116 id_, params = build_data(
117 self.apikey, 'create_repo_group',
117 self.apikey, 'create_repo_group',
118 group_name=repo_group_name,
118 group_name=repo_group_name,
119 owner=TEST_USER_ADMIN_LOGIN,)
119 owner=TEST_USER_ADMIN_LOGIN,)
120 response = api_call(self.app, params)
120 response = api_call(self.app, params)
121 expected = {
121 expected = {
122 'unique_repo_group_name':
122 'unique_repo_group_name':
123 'Repository group with name `{}` already exists'.format(
123 'Repository group with name `{}` already exists'.format(
124 repo_group_name)}
124 repo_group_name)}
125 try:
125 try:
126 assert_error(id_, expected, given=response.body)
126 assert_error(id_, expected, given=response.body)
127 finally:
127 finally:
128 fixture.destroy_repo_group(repo_group_name)
128 fixture.destroy_repo_group(repo_group_name)
129
129
130 def test_api_create_repo_group_regular_user_wit_root_location_perms(
130 def test_api_create_repo_group_regular_user_wit_root_location_perms(
131 self, user_util):
131 self, user_util):
132 regular_user = user_util.create_user()
132 regular_user = user_util.create_user()
133 regular_user_api_key = regular_user.api_key
133 regular_user_api_key = regular_user.api_key
134
134
135 repo_group_name = 'api-repo-group-by-regular-user'
135 repo_group_name = 'api-repo-group-by-regular-user'
136
136
137 usr = UserModel().get_by_username(regular_user.username)
137 usr = UserModel().get_by_username(regular_user.username)
138 usr.inherit_default_permissions = False
138 usr.inherit_default_permissions = False
139 Session().add(usr)
139 Session().add(usr)
140
140
141 UserModel().grant_perm(
141 UserModel().grant_perm(
142 regular_user.username, 'hg.repogroup.create.true')
142 regular_user.username, 'hg.repogroup.create.true')
143 Session().commit()
143 Session().commit()
144
144
145 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
145 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
146 assert repo_group is None
146 assert repo_group is None
147
147
148 id_, params = build_data(
148 id_, params = build_data(
149 regular_user_api_key, 'create_repo_group',
149 regular_user_api_key, 'create_repo_group',
150 group_name=repo_group_name)
150 group_name=repo_group_name)
151 response = api_call(self.app, params)
151 response = api_call(self.app, params)
152
152
153 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
153 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
154 assert repo_group is not None
154 assert repo_group is not None
155 expected = {
155 expected = {
156 'msg': 'Created new repo group `%s`' % (repo_group_name,),
156 'msg': 'Created new repo group `%s`' % (repo_group_name,),
157 'repo_group': repo_group.get_api_data()
157 'repo_group': repo_group.get_api_data()
158 }
158 }
159 try:
159 try:
160 assert_ok(id_, expected, given=response.body)
160 assert_ok(id_, expected, given=response.body)
161 finally:
161 finally:
162 fixture.destroy_repo_group(repo_group_name)
162 fixture.destroy_repo_group(repo_group_name)
163
163
164 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
164 def test_api_create_repo_group_regular_user_with_admin_perms_to_parent(
165 self, user_util):
165 self, user_util):
166
166
167 repo_group_name = 'api-repo-group-parent'
167 repo_group_name = 'api-repo-group-parent'
168
168
169 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
169 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
170 assert repo_group is None
170 assert repo_group is None
171 # create the parent
171 # create the parent
172 fixture.create_repo_group(repo_group_name)
172 fixture.create_repo_group(repo_group_name)
173
173
174 # user perms
174 # user perms
175 regular_user = user_util.create_user()
175 regular_user = user_util.create_user()
176 regular_user_api_key = regular_user.api_key
176 regular_user_api_key = regular_user.api_key
177
177
178 usr = UserModel().get_by_username(regular_user.username)
178 usr = UserModel().get_by_username(regular_user.username)
179 usr.inherit_default_permissions = False
179 usr.inherit_default_permissions = False
180 Session().add(usr)
180 Session().add(usr)
181
181
182 RepoGroupModel().grant_user_permission(
182 RepoGroupModel().grant_user_permission(
183 repo_group_name, regular_user.username, 'group.admin')
183 repo_group_name, regular_user.username, 'group.admin')
184 Session().commit()
184 Session().commit()
185
185
186 full_repo_group_name = repo_group_name + '/' + repo_group_name
186 full_repo_group_name = repo_group_name + '/' + repo_group_name
187 id_, params = build_data(
187 id_, params = build_data(
188 regular_user_api_key, 'create_repo_group',
188 regular_user_api_key, 'create_repo_group',
189 group_name=full_repo_group_name)
189 group_name=full_repo_group_name)
190 response = api_call(self.app, params)
190 response = api_call(self.app, params)
191
191
192 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
192 repo_group = RepoGroupModel.cls.get_by_group_name(full_repo_group_name)
193 assert repo_group is not None
193 assert repo_group is not None
194 expected = {
194 expected = {
195 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
195 'msg': 'Created new repo group `{}`'.format(full_repo_group_name),
196 'repo_group': repo_group.get_api_data()
196 'repo_group': repo_group.get_api_data()
197 }
197 }
198 try:
198 try:
199 assert_ok(id_, expected, given=response.body)
199 assert_ok(id_, expected, given=response.body)
200 finally:
200 finally:
201 fixture.destroy_repo_group(full_repo_group_name)
201 fixture.destroy_repo_group(full_repo_group_name)
202 fixture.destroy_repo_group(repo_group_name)
202 fixture.destroy_repo_group(repo_group_name)
203
203
204 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
204 def test_api_create_repo_group_regular_user_no_permission_to_create_to_root_level(self):
205 repo_group_name = 'api-repo-group'
205 repo_group_name = 'api-repo-group'
206
206
207 id_, params = build_data(
207 id_, params = build_data(
208 self.apikey_regular, 'create_repo_group',
208 self.apikey_regular, 'create_repo_group',
209 group_name=repo_group_name)
209 group_name=repo_group_name)
210 response = api_call(self.app, params)
210 response = api_call(self.app, params)
211
211
212 expected = {
212 expected = {
213 'repo_group':
213 'repo_group':
214 u'You do not have the permission to store '
214 u'You do not have the permission to store '
215 u'repository groups in the root location.'}
215 u'repository groups in the root location.'}
216 assert_error(id_, expected, given=response.body)
216 assert_error(id_, expected, given=response.body)
217
217
218 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
218 def test_api_create_repo_group_regular_user_no_parent_group_perms(self):
219 repo_group_name = 'api-repo-group-regular-user'
219 repo_group_name = 'api-repo-group-regular-user'
220
220
221 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
221 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
222 assert repo_group is None
222 assert repo_group is None
223 # create the parent
223 # create the parent
224 fixture.create_repo_group(repo_group_name)
224 fixture.create_repo_group(repo_group_name)
225
225
226 full_repo_group_name = repo_group_name+'/'+repo_group_name
226 full_repo_group_name = repo_group_name+'/'+repo_group_name
227
227
228 id_, params = build_data(
228 id_, params = build_data(
229 self.apikey_regular, 'create_repo_group',
229 self.apikey_regular, 'create_repo_group',
230 group_name=full_repo_group_name)
230 group_name=full_repo_group_name)
231 response = api_call(self.app, params)
231 response = api_call(self.app, params)
232
232
233 expected = {
233 expected = {
234 'repo_group':
234 'repo_group':
235 u"You do not have the permissions to store "
235 u"You do not have the permissions to store "
236 u"repository groups inside repository group `{}`".format(repo_group_name)}
236 u"repository groups inside repository group `{}`".format(repo_group_name)}
237 try:
237 try:
238 assert_error(id_, expected, given=response.body)
238 assert_error(id_, expected, given=response.body)
239 finally:
239 finally:
240 fixture.destroy_repo_group(repo_group_name)
240 fixture.destroy_repo_group(repo_group_name)
241
241
242 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
242 def test_api_create_repo_group_regular_user_no_permission_to_specify_owner(
243 self):
243 self):
244 repo_group_name = 'api-repo-group'
244 repo_group_name = 'api-repo-group'
245
245
246 id_, params = build_data(
246 id_, params = build_data(
247 self.apikey_regular, 'create_repo_group',
247 self.apikey_regular, 'create_repo_group',
248 group_name=repo_group_name,
248 group_name=repo_group_name,
249 owner=TEST_USER_ADMIN_LOGIN,)
249 owner=TEST_USER_ADMIN_LOGIN,)
250 response = api_call(self.app, params)
250 response = api_call(self.app, params)
251
251
252 expected = "Only RhodeCode super-admin can specify `owner` param"
252 expected = "Only RhodeCode super-admin can specify `owner` param"
253 assert_error(id_, expected, given=response.body)
253 assert_error(id_, expected, given=response.body)
254
254
255 @mock.patch.object(RepoGroupModel, 'create', crash)
255 @mock.patch.object(RepoGroupModel, 'create', crash)
256 def test_api_create_repo_group_exception_occurred(self):
256 def test_api_create_repo_group_exception_occurred(self):
257 repo_group_name = 'api-repo-group'
257 repo_group_name = 'api-repo-group'
258
258
259 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
259 repo_group = RepoGroupModel.cls.get_by_group_name(repo_group_name)
260 assert repo_group is None
260 assert repo_group is None
261
261
262 id_, params = build_data(
262 id_, params = build_data(
263 self.apikey, 'create_repo_group',
263 self.apikey, 'create_repo_group',
264 group_name=repo_group_name,
264 group_name=repo_group_name,
265 owner=TEST_USER_ADMIN_LOGIN,)
265 owner=TEST_USER_ADMIN_LOGIN,)
266 response = api_call(self.app, params)
266 response = api_call(self.app, params)
267 expected = 'failed to create repo group `%s`' % (repo_group_name,)
267 expected = 'failed to create repo group `%s`' % (repo_group_name,)
268 assert_error(id_, expected, given=response.body)
268 assert_error(id_, expected, given=response.body)
269
269
270 def test_create_group_with_extra_slashes_in_name(self, user_util):
270 def test_create_group_with_extra_slashes_in_name(self, user_util):
271 existing_repo_group = user_util.create_repo_group()
271 existing_repo_group = user_util.create_repo_group()
272 dirty_group_name = '//{}//group2//'.format(
272 dirty_group_name = '//{}//group2//'.format(
273 existing_repo_group.group_name)
273 existing_repo_group.group_name)
274 cleaned_group_name = '{}/group2'.format(
274 cleaned_group_name = '{}/group2'.format(
275 existing_repo_group.group_name)
275 existing_repo_group.group_name)
276
276
277 id_, params = build_data(
277 id_, params = build_data(
278 self.apikey, 'create_repo_group',
278 self.apikey, 'create_repo_group',
279 group_name=dirty_group_name,
279 group_name=dirty_group_name,
280 owner=TEST_USER_ADMIN_LOGIN,)
280 owner=TEST_USER_ADMIN_LOGIN,)
281 response = api_call(self.app, params)
281 response = api_call(self.app, params)
282 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
282 repo_group = RepoGroupModel.cls.get_by_group_name(cleaned_group_name)
283 expected = {
283 expected = {
284 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
284 'msg': 'Created new repo group `%s`' % (cleaned_group_name,),
285 'repo_group': repo_group.get_api_data()
285 'repo_group': repo_group.get_api_data()
286 }
286 }
287 assert_ok(id_, expected, given=response.body)
287 assert_ok(id_, expected, given=response.body)
288 fixture.destroy_repo_group(cleaned_group_name)
288 fixture.destroy_repo_group(cleaned_group_name)
@@ -1,206 +1,206 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.auth import check_password
23 from rhodecode.lib.auth import check_password
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.tests import (
25 from rhodecode.tests import (
26 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
26 TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_EMAIL)
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error, jsonify, crash)
28 build_data, api_call, assert_ok, assert_error, jsonify, crash)
29 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture import Fixture
30 from rhodecode.model.db import RepoGroup
30 from rhodecode.model.db import RepoGroup
31
31
32
32
33 # TODO: mikhail: remove fixture from here
33 # TODO: mikhail: remove fixture from here
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36
36
37 @pytest.mark.usefixtures("testuser_api", "app")
37 @pytest.mark.usefixtures("testuser_api", "app")
38 class TestCreateUser(object):
38 class TestCreateUser(object):
39 def test_api_create_existing_user(self):
39 def test_api_create_existing_user(self):
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey, 'create_user',
41 self.apikey, 'create_user',
42 username=TEST_USER_ADMIN_LOGIN,
42 username=TEST_USER_ADMIN_LOGIN,
43 email='test@foo.com',
43 email='test@foo.com',
44 password='trololo')
44 password='trololo')
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
47 expected = "user `%s` already exist" % (TEST_USER_ADMIN_LOGIN,)
48 assert_error(id_, expected, given=response.body)
48 assert_error(id_, expected, given=response.body)
49
49
50 def test_api_create_user_with_existing_email(self):
50 def test_api_create_user_with_existing_email(self):
51 id_, params = build_data(
51 id_, params = build_data(
52 self.apikey, 'create_user',
52 self.apikey, 'create_user',
53 username=TEST_USER_ADMIN_LOGIN + 'new',
53 username=TEST_USER_ADMIN_LOGIN + 'new',
54 email=TEST_USER_REGULAR_EMAIL,
54 email=TEST_USER_REGULAR_EMAIL,
55 password='trololo')
55 password='trololo')
56 response = api_call(self.app, params)
56 response = api_call(self.app, params)
57
57
58 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
58 expected = "email `%s` already exist" % (TEST_USER_REGULAR_EMAIL,)
59 assert_error(id_, expected, given=response.body)
59 assert_error(id_, expected, given=response.body)
60
60
61 def test_api_create_user_with_wrong_username(self):
61 def test_api_create_user_with_wrong_username(self):
62 bad_username = '<> HELLO WORLD <>'
62 bad_username = '<> HELLO WORLD <>'
63 id_, params = build_data(
63 id_, params = build_data(
64 self.apikey, 'create_user',
64 self.apikey, 'create_user',
65 username=bad_username,
65 username=bad_username,
66 email='new@email.com',
66 email='new@email.com',
67 password='trololo')
67 password='trololo')
68 response = api_call(self.app, params)
68 response = api_call(self.app, params)
69
69
70 expected = {'username':
70 expected = {'username':
71 "Username may only contain alphanumeric characters "
71 "Username may only contain alphanumeric characters "
72 "underscores, periods or dashes and must begin with "
72 "underscores, periods or dashes and must begin with "
73 "alphanumeric character or underscore"}
73 "alphanumeric character or underscore"}
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
75
75
76 def test_api_create_user(self):
76 def test_api_create_user(self):
77 username = 'test_new_api_user'
77 username = 'test_new_api_user'
78 email = username + "@foo.com"
78 email = username + "@foo.com"
79
79
80 id_, params = build_data(
80 id_, params = build_data(
81 self.apikey, 'create_user',
81 self.apikey, 'create_user',
82 username=username,
82 username=username,
83 email=email,
83 email=email,
84 description='CTO of Things',
84 description='CTO of Things',
85 password='example')
85 password='example')
86 response = api_call(self.app, params)
86 response = api_call(self.app, params)
87
87
88 usr = UserModel().get_by_username(username)
88 usr = UserModel().get_by_username(username)
89 ret = {
89 ret = {
90 'msg': 'created new user `%s`' % (username,),
90 'msg': 'created new user `%s`' % (username,),
91 'user': jsonify(usr.get_api_data(include_secrets=True)),
91 'user': jsonify(usr.get_api_data(include_secrets=True)),
92 }
92 }
93 try:
93 try:
94 expected = ret
94 expected = ret
95 assert check_password('example', usr.password)
95 assert check_password('example', usr.password)
96 assert_ok(id_, expected, given=response.body)
96 assert_ok(id_, expected, given=response.body)
97 finally:
97 finally:
98 fixture.destroy_user(usr.user_id)
98 fixture.destroy_user(usr.user_id)
99
99
100 def test_api_create_user_without_password(self):
100 def test_api_create_user_without_password(self):
101 username = 'test_new_api_user_passwordless'
101 username = 'test_new_api_user_passwordless'
102 email = username + "@foo.com"
102 email = username + "@foo.com"
103
103
104 id_, params = build_data(
104 id_, params = build_data(
105 self.apikey, 'create_user',
105 self.apikey, 'create_user',
106 username=username,
106 username=username,
107 email=email)
107 email=email)
108 response = api_call(self.app, params)
108 response = api_call(self.app, params)
109
109
110 usr = UserModel().get_by_username(username)
110 usr = UserModel().get_by_username(username)
111 ret = {
111 ret = {
112 'msg': 'created new user `%s`' % (username,),
112 'msg': 'created new user `%s`' % (username,),
113 'user': jsonify(usr.get_api_data(include_secrets=True)),
113 'user': jsonify(usr.get_api_data(include_secrets=True)),
114 }
114 }
115 try:
115 try:
116 expected = ret
116 expected = ret
117 assert_ok(id_, expected, given=response.body)
117 assert_ok(id_, expected, given=response.body)
118 finally:
118 finally:
119 fixture.destroy_user(usr.user_id)
119 fixture.destroy_user(usr.user_id)
120
120
121 def test_api_create_user_with_extern_name(self):
121 def test_api_create_user_with_extern_name(self):
122 username = 'test_new_api_user_passwordless'
122 username = 'test_new_api_user_passwordless'
123 email = username + "@foo.com"
123 email = username + "@foo.com"
124
124
125 id_, params = build_data(
125 id_, params = build_data(
126 self.apikey, 'create_user',
126 self.apikey, 'create_user',
127 username=username,
127 username=username,
128 email=email, extern_name='rhodecode')
128 email=email, extern_name='rhodecode')
129 response = api_call(self.app, params)
129 response = api_call(self.app, params)
130
130
131 usr = UserModel().get_by_username(username)
131 usr = UserModel().get_by_username(username)
132 ret = {
132 ret = {
133 'msg': 'created new user `%s`' % (username,),
133 'msg': 'created new user `%s`' % (username,),
134 'user': jsonify(usr.get_api_data(include_secrets=True)),
134 'user': jsonify(usr.get_api_data(include_secrets=True)),
135 }
135 }
136 try:
136 try:
137 expected = ret
137 expected = ret
138 assert_ok(id_, expected, given=response.body)
138 assert_ok(id_, expected, given=response.body)
139 finally:
139 finally:
140 fixture.destroy_user(usr.user_id)
140 fixture.destroy_user(usr.user_id)
141
141
142 def test_api_create_user_with_password_change(self):
142 def test_api_create_user_with_password_change(self):
143 username = 'test_new_api_user_password_change'
143 username = 'test_new_api_user_password_change'
144 email = username + "@foo.com"
144 email = username + "@foo.com"
145
145
146 id_, params = build_data(
146 id_, params = build_data(
147 self.apikey, 'create_user',
147 self.apikey, 'create_user',
148 username=username,
148 username=username,
149 email=email, extern_name='rhodecode',
149 email=email, extern_name='rhodecode',
150 force_password_change=True)
150 force_password_change=True)
151 response = api_call(self.app, params)
151 response = api_call(self.app, params)
152
152
153 usr = UserModel().get_by_username(username)
153 usr = UserModel().get_by_username(username)
154 ret = {
154 ret = {
155 'msg': 'created new user `%s`' % (username,),
155 'msg': 'created new user `%s`' % (username,),
156 'user': jsonify(usr.get_api_data(include_secrets=True)),
156 'user': jsonify(usr.get_api_data(include_secrets=True)),
157 }
157 }
158 try:
158 try:
159 expected = ret
159 expected = ret
160 assert_ok(id_, expected, given=response.body)
160 assert_ok(id_, expected, given=response.body)
161 finally:
161 finally:
162 fixture.destroy_user(usr.user_id)
162 fixture.destroy_user(usr.user_id)
163
163
164 def test_api_create_user_with_personal_repo_group(self):
164 def test_api_create_user_with_personal_repo_group(self):
165 username = 'test_new_api_user_personal_group'
165 username = 'test_new_api_user_personal_group'
166 email = username + "@foo.com"
166 email = username + "@foo.com"
167
167
168 id_, params = build_data(
168 id_, params = build_data(
169 self.apikey, 'create_user',
169 self.apikey, 'create_user',
170 username=username,
170 username=username,
171 email=email, extern_name='rhodecode',
171 email=email, extern_name='rhodecode',
172 create_personal_repo_group=True)
172 create_personal_repo_group=True)
173 response = api_call(self.app, params)
173 response = api_call(self.app, params)
174
174
175 usr = UserModel().get_by_username(username)
175 usr = UserModel().get_by_username(username)
176 ret = {
176 ret = {
177 'msg': 'created new user `%s`' % (username,),
177 'msg': 'created new user `%s`' % (username,),
178 'user': jsonify(usr.get_api_data(include_secrets=True)),
178 'user': jsonify(usr.get_api_data(include_secrets=True)),
179 }
179 }
180
180
181 personal_group = RepoGroup.get_by_group_name(username)
181 personal_group = RepoGroup.get_by_group_name(username)
182 assert personal_group
182 assert personal_group
183 assert personal_group.personal == True
183 assert personal_group.personal == True
184 assert personal_group.user.username == username
184 assert personal_group.user.username == username
185
185
186 try:
186 try:
187 expected = ret
187 expected = ret
188 assert_ok(id_, expected, given=response.body)
188 assert_ok(id_, expected, given=response.body)
189 finally:
189 finally:
190 fixture.destroy_repo_group(username)
190 fixture.destroy_repo_group(username)
191 fixture.destroy_user(usr.user_id)
191 fixture.destroy_user(usr.user_id)
192
192
193 @mock.patch.object(UserModel, 'create_or_update', crash)
193 @mock.patch.object(UserModel, 'create_or_update', crash)
194 def test_api_create_user_when_exception_happened(self):
194 def test_api_create_user_when_exception_happened(self):
195
195
196 username = 'test_new_api_user'
196 username = 'test_new_api_user'
197 email = username + "@foo.com"
197 email = username + "@foo.com"
198
198
199 id_, params = build_data(
199 id_, params = build_data(
200 self.apikey, 'create_user',
200 self.apikey, 'create_user',
201 username=username,
201 username=username,
202 email=email,
202 email=email,
203 password='trololo')
203 password='trololo')
204 response = api_call(self.app, params)
204 response = api_call(self.app, params)
205 expected = 'failed to create user `%s`' % (username,)
205 expected = 'failed to create user `%s`' % (username,)
206 assert_error(id_, expected, given=response.body)
206 assert_error(id_, expected, given=response.body)
@@ -1,126 +1,126 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestCreateUserGroup(object):
32 class TestCreateUserGroup(object):
33 fixture = Fixture()
33 fixture = Fixture()
34
34
35 def test_api_create_user_group(self):
35 def test_api_create_user_group(self):
36 group_name = 'some_new_group'
36 group_name = 'some_new_group'
37 id_, params = build_data(
37 id_, params = build_data(
38 self.apikey, 'create_user_group', group_name=group_name)
38 self.apikey, 'create_user_group', group_name=group_name)
39 response = api_call(self.app, params)
39 response = api_call(self.app, params)
40
40
41 ret = {
41 ret = {
42 'msg': 'created new user group `%s`' % (group_name,),
42 'msg': 'created new user group `%s`' % (group_name,),
43 'user_group': jsonify(
43 'user_group': jsonify(
44 UserGroupModel()
44 UserGroupModel()
45 .get_by_name(group_name)
45 .get_by_name(group_name)
46 .get_api_data()
46 .get_api_data()
47 )
47 )
48 }
48 }
49 expected = ret
49 expected = ret
50 assert_ok(id_, expected, given=response.body)
50 assert_ok(id_, expected, given=response.body)
51 self.fixture.destroy_user_group(group_name)
51 self.fixture.destroy_user_group(group_name)
52
52
53 def test_api_create_user_group_regular_user(self):
53 def test_api_create_user_group_regular_user(self):
54 group_name = 'some_new_group'
54 group_name = 'some_new_group'
55
55
56 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
56 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 usr.inherit_default_permissions = False
57 usr.inherit_default_permissions = False
58 Session().add(usr)
58 Session().add(usr)
59 UserModel().grant_perm(
59 UserModel().grant_perm(
60 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
60 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
61 Session().commit()
61 Session().commit()
62
62
63 id_, params = build_data(
63 id_, params = build_data(
64 self.apikey_regular, 'create_user_group', group_name=group_name)
64 self.apikey_regular, 'create_user_group', group_name=group_name)
65 response = api_call(self.app, params)
65 response = api_call(self.app, params)
66
66
67 expected = {
67 expected = {
68 'msg': 'created new user group `%s`' % (group_name,),
68 'msg': 'created new user group `%s`' % (group_name,),
69 'user_group': jsonify(
69 'user_group': jsonify(
70 UserGroupModel()
70 UserGroupModel()
71 .get_by_name(group_name)
71 .get_by_name(group_name)
72 .get_api_data()
72 .get_api_data()
73 )
73 )
74 }
74 }
75 try:
75 try:
76 assert_ok(id_, expected, given=response.body)
76 assert_ok(id_, expected, given=response.body)
77 finally:
77 finally:
78 self.fixture.destroy_user_group(group_name)
78 self.fixture.destroy_user_group(group_name)
79 UserModel().revoke_perm(
79 UserModel().revoke_perm(
80 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
80 self.TEST_USER_LOGIN, 'hg.usergroup.create.true')
81 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
81 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
82 usr.inherit_default_permissions = True
82 usr.inherit_default_permissions = True
83 Session().add(usr)
83 Session().add(usr)
84 Session().commit()
84 Session().commit()
85
85
86 def test_api_create_user_group_regular_user_no_permission(self):
86 def test_api_create_user_group_regular_user_no_permission(self):
87 group_name = 'some_new_group'
87 group_name = 'some_new_group'
88 id_, params = build_data(
88 id_, params = build_data(
89 self.apikey_regular, 'create_user_group', group_name=group_name)
89 self.apikey_regular, 'create_user_group', group_name=group_name)
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91 expected = "Access was denied to this resource."
91 expected = "Access was denied to this resource."
92 assert_error(id_, expected, given=response.body)
92 assert_error(id_, expected, given=response.body)
93
93
94 def test_api_create_user_group_that_exist(self, user_util):
94 def test_api_create_user_group_that_exist(self, user_util):
95 group = user_util.create_user_group()
95 group = user_util.create_user_group()
96 group_name = group.users_group_name
96 group_name = group.users_group_name
97
97
98 id_, params = build_data(
98 id_, params = build_data(
99 self.apikey, 'create_user_group', group_name=group_name)
99 self.apikey, 'create_user_group', group_name=group_name)
100 response = api_call(self.app, params)
100 response = api_call(self.app, params)
101
101
102 expected = "user group `%s` already exist" % (group_name,)
102 expected = "user group `%s` already exist" % (group_name,)
103 assert_error(id_, expected, given=response.body)
103 assert_error(id_, expected, given=response.body)
104
104
105 @mock.patch.object(UserGroupModel, 'create', crash)
105 @mock.patch.object(UserGroupModel, 'create', crash)
106 def test_api_create_user_group_exception_occurred(self):
106 def test_api_create_user_group_exception_occurred(self):
107 group_name = 'exception_happens'
107 group_name = 'exception_happens'
108 id_, params = build_data(
108 id_, params = build_data(
109 self.apikey, 'create_user_group', group_name=group_name)
109 self.apikey, 'create_user_group', group_name=group_name)
110 response = api_call(self.app, params)
110 response = api_call(self.app, params)
111
111
112 expected = 'failed to create group `%s`' % (group_name,)
112 expected = 'failed to create group `%s`' % (group_name,)
113 assert_error(id_, expected, given=response.body)
113 assert_error(id_, expected, given=response.body)
114
114
115 def test_api_create_user_group_with_wrong_name(self, user_util):
115 def test_api_create_user_group_with_wrong_name(self, user_util):
116
116
117 group_name = 'wrong NAME <>'
117 group_name = 'wrong NAME <>'
118 id_, params = build_data(
118 id_, params = build_data(
119 self.apikey, 'create_user_group', group_name=group_name)
119 self.apikey, 'create_user_group', group_name=group_name)
120 response = api_call(self.app, params)
120 response = api_call(self.app, params)
121
121
122 expected = {"user_group_name":
122 expected = {"user_group_name":
123 "Allowed in name are letters, numbers, and `-`, `_`, "
123 "Allowed in name are letters, numbers, and `-`, `_`, "
124 "`.` Name must start with a letter or number. "
124 "`.` Name must start with a letter or number. "
125 "Got `{}`".format(group_name)}
125 "Got `{}`".format(group_name)}
126 assert_error(id_, expected, given=response.body)
126 assert_error(id_, expected, given=response.body)
@@ -1,60 +1,60 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.gist import GistModel
23 from rhodecode.model.gist import GistModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApiDeleteGist(object):
29 class TestApiDeleteGist(object):
30 def test_api_delete_gist(self, gist_util):
30 def test_api_delete_gist(self, gist_util):
31 gist_id = gist_util.create_gist().gist_access_id
31 gist_id = gist_util.create_gist().gist_access_id
32 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
32 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
33 response = api_call(self.app, params)
33 response = api_call(self.app, params)
34 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
34 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
35 assert_ok(id_, expected, given=response.body)
35 assert_ok(id_, expected, given=response.body)
36
36
37 def test_api_delete_gist_regular_user(self, gist_util):
37 def test_api_delete_gist_regular_user(self, gist_util):
38 gist_id = gist_util.create_gist(
38 gist_id = gist_util.create_gist(
39 owner=self.TEST_USER_LOGIN).gist_access_id
39 owner=self.TEST_USER_LOGIN).gist_access_id
40 id_, params = build_data(
40 id_, params = build_data(
41 self.apikey_regular, 'delete_gist', gistid=gist_id)
41 self.apikey_regular, 'delete_gist', gistid=gist_id)
42 response = api_call(self.app, params)
42 response = api_call(self.app, params)
43 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
43 expected = {'gist': None, 'msg': 'deleted gist ID:%s' % (gist_id,)}
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 def test_api_delete_gist_regular_user_no_permission(self, gist_util):
46 def test_api_delete_gist_regular_user_no_permission(self, gist_util):
47 gist_id = gist_util.create_gist().gist_access_id
47 gist_id = gist_util.create_gist().gist_access_id
48 id_, params = build_data(
48 id_, params = build_data(
49 self.apikey_regular, 'delete_gist', gistid=gist_id)
49 self.apikey_regular, 'delete_gist', gistid=gist_id)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51 expected = 'gist `%s` does not exist' % (gist_id,)
51 expected = 'gist `%s` does not exist' % (gist_id,)
52 assert_error(id_, expected, given=response.body)
52 assert_error(id_, expected, given=response.body)
53
53
54 @mock.patch.object(GistModel, 'delete', crash)
54 @mock.patch.object(GistModel, 'delete', crash)
55 def test_api_delete_gist_exception_occurred(self, gist_util):
55 def test_api_delete_gist_exception_occurred(self, gist_util):
56 gist_id = gist_util.create_gist().gist_access_id
56 gist_id = gist_util.create_gist().gist_access_id
57 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
57 id_, params = build_data(self.apikey, 'delete_gist', gistid=gist_id)
58 response = api_call(self.app, params)
58 response = api_call(self.app, params)
59 expected = 'failed to delete gist ID:%s' % (gist_id,)
59 expected = 'failed to delete gist ID:%s' % (gist_id,)
60 assert_error(id_, expected, given=response.body)
60 assert_error(id_, expected, given=response.body)
@@ -1,73 +1,73 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo import RepoModel
23 from rhodecode.model.repo import RepoModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApiDeleteRepo(object):
29 class TestApiDeleteRepo(object):
30 def test_api_delete_repo(self, backend):
30 def test_api_delete_repo(self, backend):
31 repo = backend.create_repo()
31 repo = backend.create_repo()
32 repo_name = repo.repo_name
32 repo_name = repo.repo_name
33 id_, params = build_data(
33 id_, params = build_data(
34 self.apikey, 'delete_repo', repoid=repo.repo_name, )
34 self.apikey, 'delete_repo', repoid=repo.repo_name, )
35 response = api_call(self.app, params)
35 response = api_call(self.app, params)
36
36
37 expected = {
37 expected = {
38 'msg': 'Deleted repository `%s`' % (repo_name,),
38 'msg': 'Deleted repository `%s`' % (repo_name,),
39 'success': True
39 'success': True
40 }
40 }
41 assert_ok(id_, expected, given=response.body)
41 assert_ok(id_, expected, given=response.body)
42
42
43 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
43 def test_api_delete_repo_by_non_admin(self, backend, user_regular):
44 repo = backend.create_repo(cur_user=user_regular.username)
44 repo = backend.create_repo(cur_user=user_regular.username)
45 repo_name = repo.repo_name
45 repo_name = repo.repo_name
46 id_, params = build_data(
46 id_, params = build_data(
47 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
47 user_regular.api_key, 'delete_repo', repoid=repo.repo_name, )
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = {
50 expected = {
51 'msg': 'Deleted repository `%s`' % (repo_name,),
51 'msg': 'Deleted repository `%s`' % (repo_name,),
52 'success': True
52 'success': True
53 }
53 }
54 assert_ok(id_, expected, given=response.body)
54 assert_ok(id_, expected, given=response.body)
55
55
56 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
56 def test_api_delete_repo_by_non_admin_no_permission(self, backend):
57 repo = backend.create_repo()
57 repo = backend.create_repo()
58 repo_name = repo.repo_name
58 repo_name = repo.repo_name
59 id_, params = build_data(
59 id_, params = build_data(
60 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
60 self.apikey_regular, 'delete_repo', repoid=repo.repo_name, )
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62 expected = 'repository `%s` does not exist' % (repo_name)
62 expected = 'repository `%s` does not exist' % (repo_name)
63 assert_error(id_, expected, given=response.body)
63 assert_error(id_, expected, given=response.body)
64
64
65 def test_api_delete_repo_exception_occurred(self, backend):
65 def test_api_delete_repo_exception_occurred(self, backend):
66 repo = backend.create_repo()
66 repo = backend.create_repo()
67 repo_name = repo.repo_name
67 repo_name = repo.repo_name
68 id_, params = build_data(
68 id_, params = build_data(
69 self.apikey, 'delete_repo', repoid=repo.repo_name, )
69 self.apikey, 'delete_repo', repoid=repo.repo_name, )
70 with mock.patch.object(RepoModel, 'delete', crash):
70 with mock.patch.object(RepoModel, 'delete', crash):
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72 expected = 'failed to delete repository `%s`' % (repo_name,)
72 expected = 'failed to delete repository `%s`' % (repo_name,)
73 assert_error(id_, expected, given=response.body)
73 assert_error(id_, expected, given=response.body)
@@ -1,85 +1,85 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo_group import RepoGroupModel
23 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiDeleteRepoGroup(object):
30 class TestApiDeleteRepoGroup(object):
31 def test_api_delete_repo_group(self, user_util):
31 def test_api_delete_repo_group(self, user_util):
32 repo_group = user_util.create_repo_group(auto_cleanup=False)
32 repo_group = user_util.create_repo_group(auto_cleanup=False)
33 repo_group_name = repo_group.group_name
33 repo_group_name = repo_group.group_name
34 repo_group_id = repo_group.group_id
34 repo_group_id = repo_group.group_id
35 id_, params = build_data(
35 id_, params = build_data(
36 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
36 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
37 response = api_call(self.app, params)
37 response = api_call(self.app, params)
38
38
39 ret = {
39 ret = {
40 'msg': 'deleted repo group ID:%s %s' % (
40 'msg': 'deleted repo group ID:%s %s' % (
41 repo_group_id, repo_group_name
41 repo_group_id, repo_group_name
42 ),
42 ),
43 'repo_group': None
43 'repo_group': None
44 }
44 }
45 expected = ret
45 expected = ret
46 assert_ok(id_, expected, given=response.body)
46 assert_ok(id_, expected, given=response.body)
47 gr = RepoGroupModel()._get_repo_group(repo_group_name)
47 gr = RepoGroupModel()._get_repo_group(repo_group_name)
48 assert gr is None
48 assert gr is None
49
49
50 def test_api_delete_repo_group_regular_user(self, user_util):
50 def test_api_delete_repo_group_regular_user(self, user_util):
51 repo_group = user_util.create_repo_group(auto_cleanup=False)
51 repo_group = user_util.create_repo_group(auto_cleanup=False)
52 repo_group_name = repo_group.group_name
52 repo_group_name = repo_group.group_name
53 repo_group_id = repo_group.group_id
53 repo_group_id = repo_group.group_id
54
54
55 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
55 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
56 user_util.grant_user_permission_to_repo_group(
56 user_util.grant_user_permission_to_repo_group(
57 repo_group, user, 'group.admin')
57 repo_group, user, 'group.admin')
58
58
59 id_, params = build_data(
59 id_, params = build_data(
60 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
60 self.apikey, 'delete_repo_group', repogroupid=repo_group_name, )
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62
62
63 ret = {
63 ret = {
64 'msg': 'deleted repo group ID:%s %s' % (
64 'msg': 'deleted repo group ID:%s %s' % (
65 repo_group_id, repo_group_name
65 repo_group_id, repo_group_name
66 ),
66 ),
67 'repo_group': None
67 'repo_group': None
68 }
68 }
69 expected = ret
69 expected = ret
70 assert_ok(id_, expected, given=response.body)
70 assert_ok(id_, expected, given=response.body)
71 gr = RepoGroupModel()._get_repo_group(repo_group_name)
71 gr = RepoGroupModel()._get_repo_group(repo_group_name)
72 assert gr is None
72 assert gr is None
73
73
74 def test_api_delete_repo_group_regular_user_no_permission(self, user_util):
74 def test_api_delete_repo_group_regular_user_no_permission(self, user_util):
75 repo_group = user_util.create_repo_group()
75 repo_group = user_util.create_repo_group()
76 repo_group_name = repo_group.group_name
76 repo_group_name = repo_group.group_name
77
77
78 id_, params = build_data(
78 id_, params = build_data(
79 self.apikey_regular, 'delete_repo_group',
79 self.apikey_regular, 'delete_repo_group',
80 repogroupid=repo_group_name, )
80 repogroupid=repo_group_name, )
81 response = api_call(self.app, params)
81 response = api_call(self.app, params)
82
82
83 expected = 'repository group `%s` does not exist' % (
83 expected = 'repository group `%s` does not exist' % (
84 repo_group_name,)
84 repo_group_name,)
85 assert_error(id_, expected, given=response.body)
85 assert_error(id_, expected, given=response.body)
@@ -1,56 +1,56 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error, crash)
26 build_data, api_call, assert_ok, assert_error, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestDeleteUser(object):
30 class TestDeleteUser(object):
31 def test_api_delete_user(self, user_util):
31 def test_api_delete_user(self, user_util):
32 usr = user_util.create_user(auto_cleanup=False)
32 usr = user_util.create_user(auto_cleanup=False)
33
33
34 username = usr.username
34 username = usr.username
35 usr_id = usr.user_id
35 usr_id = usr.user_id
36
36
37 id_, params = build_data(self.apikey, 'delete_user', userid=username)
37 id_, params = build_data(self.apikey, 'delete_user', userid=username)
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
40 ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username),
41 'user': None}
41 'user': None}
42 expected = ret
42 expected = ret
43 assert_ok(id_, expected, given=response.body)
43 assert_ok(id_, expected, given=response.body)
44
44
45 @mock.patch.object(UserModel, 'delete', crash)
45 @mock.patch.object(UserModel, 'delete', crash)
46 def test_api_delete_user_when_exception_happened(self, user_util):
46 def test_api_delete_user_when_exception_happened(self, user_util):
47 usr = user_util.create_user()
47 usr = user_util.create_user()
48 username = usr.username
48 username = usr.username
49
49
50 id_, params = build_data(
50 id_, params = build_data(
51 self.apikey, 'delete_user', userid=username, )
51 self.apikey, 'delete_user', userid=username, )
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
53 ret = 'failed to delete user ID:%s %s' % (usr.user_id,
54 usr.username)
54 usr.username)
55 expected = ret
55 expected = ret
56 assert_error(id_, expected, given=response.body)
56 assert_error(id_, expected, given=response.body)
@@ -1,105 +1,105 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.model.user_group import UserGroupModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash)
27 build_data, api_call, assert_error, assert_ok, crash)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestDeleteUserGroup(object):
31 class TestDeleteUserGroup(object):
32 def test_api_delete_user_group(self, user_util):
32 def test_api_delete_user_group(self, user_util):
33 user_group = user_util.create_user_group(auto_cleanup=False)
33 user_group = user_util.create_user_group(auto_cleanup=False)
34 group_name = user_group.users_group_name
34 group_name = user_group.users_group_name
35 group_id = user_group.users_group_id
35 group_id = user_group.users_group_id
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'delete_user_group', usergroupid=group_name)
37 self.apikey, 'delete_user_group', usergroupid=group_name)
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = {
40 expected = {
41 'user_group': None,
41 'user_group': None,
42 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
42 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
43 }
43 }
44 assert_ok(id_, expected, given=response.body)
44 assert_ok(id_, expected, given=response.body)
45
45
46 def test_api_delete_user_group_regular_user(self, user_util):
46 def test_api_delete_user_group_regular_user(self, user_util):
47 ugroup = user_util.create_user_group(auto_cleanup=False)
47 ugroup = user_util.create_user_group(auto_cleanup=False)
48 group_name = ugroup.users_group_name
48 group_name = ugroup.users_group_name
49 group_id = ugroup.users_group_id
49 group_id = ugroup.users_group_id
50 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
50 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
51
51
52 user_util.grant_user_permission_to_user_group(
52 user_util.grant_user_permission_to_user_group(
53 ugroup, user, 'usergroup.admin')
53 ugroup, user, 'usergroup.admin')
54
54
55 id_, params = build_data(
55 id_, params = build_data(
56 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
56 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
57 response = api_call(self.app, params)
57 response = api_call(self.app, params)
58
58
59 expected = {
59 expected = {
60 'user_group': None,
60 'user_group': None,
61 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
61 'msg': 'deleted user group ID:%s %s' % (group_id, group_name)
62 }
62 }
63 assert_ok(id_, expected, given=response.body)
63 assert_ok(id_, expected, given=response.body)
64
64
65 def test_api_delete_user_group_regular_user_no_permission(self, user_util):
65 def test_api_delete_user_group_regular_user_no_permission(self, user_util):
66 user_group = user_util.create_user_group()
66 user_group = user_util.create_user_group()
67 group_name = user_group.users_group_name
67 group_name = user_group.users_group_name
68
68
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
70 self.apikey_regular, 'delete_user_group', usergroupid=group_name)
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72
72
73 expected = 'user group `%s` does not exist' % (group_name)
73 expected = 'user group `%s` does not exist' % (group_name)
74 assert_error(id_, expected, given=response.body)
74 assert_error(id_, expected, given=response.body)
75
75
76 def test_api_delete_user_group_that_is_assigned(self, backend, user_util):
76 def test_api_delete_user_group_that_is_assigned(self, backend, user_util):
77 ugroup = user_util.create_user_group()
77 ugroup = user_util.create_user_group()
78 group_name = ugroup.users_group_name
78 group_name = ugroup.users_group_name
79 repo = backend.create_repo()
79 repo = backend.create_repo()
80
80
81 ugr_to_perm = user_util.grant_user_group_permission_to_repo(
81 ugr_to_perm = user_util.grant_user_group_permission_to_repo(
82 repo, ugroup, 'repository.write')
82 repo, ugroup, 'repository.write')
83 msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository)
83 msg = 'UserGroup assigned to %s' % (ugr_to_perm.repository)
84
84
85 id_, params = build_data(
85 id_, params = build_data(
86 self.apikey, 'delete_user_group',
86 self.apikey, 'delete_user_group',
87 usergroupid=group_name)
87 usergroupid=group_name)
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89
89
90 expected = msg
90 expected = msg
91 assert_error(id_, expected, given=response.body)
91 assert_error(id_, expected, given=response.body)
92
92
93 def test_api_delete_user_group_exception_occurred(self, user_util):
93 def test_api_delete_user_group_exception_occurred(self, user_util):
94 ugroup = user_util.create_user_group()
94 ugroup = user_util.create_user_group()
95 group_name = ugroup.users_group_name
95 group_name = ugroup.users_group_name
96 group_id = ugroup.users_group_id
96 group_id = ugroup.users_group_id
97 id_, params = build_data(
97 id_, params = build_data(
98 self.apikey, 'delete_user_group',
98 self.apikey, 'delete_user_group',
99 usergroupid=group_name)
99 usergroupid=group_name)
100
100
101 with mock.patch.object(UserGroupModel, 'delete', crash):
101 with mock.patch.object(UserGroupModel, 'delete', crash):
102 response = api_call(self.app, params)
102 response = api_call(self.app, params)
103 expected = 'failed to delete user group ID:%s %s' % (
103 expected = 'failed to delete user group ID:%s %s' % (
104 group_id, group_name)
104 group_id, group_name)
105 assert_error(id_, expected, given=response.body)
105 assert_error(id_, expected, given=response.body)
@@ -1,78 +1,78 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.views import deprecated_api
23 from rhodecode.api.views import deprecated_api
24 from rhodecode.lib.ext_json import json
24 from rhodecode.lib.ext_json import json
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call)
26 build_data, api_call)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestCommitComment(object):
30 class TestCommitComment(object):
31 def test_deprecated_message_in_docstring(self):
31 def test_deprecated_message_in_docstring(self):
32 docstring = deprecated_api.changeset_comment.__doc__
32 docstring = deprecated_api.changeset_comment.__doc__
33 assert '.. deprecated:: 3.4.0' in docstring
33 assert '.. deprecated:: 3.4.0' in docstring
34 assert 'Please use method `comment_commit` instead.' in docstring
34 assert 'Please use method `comment_commit` instead.' in docstring
35
35
36 def test_deprecated_message_in_retvalue(self):
36 def test_deprecated_message_in_retvalue(self):
37
37
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey, 'show_ip')
39 self.apikey, 'show_ip')
40 response = api_call(self.app, params)
40 response = api_call(self.app, params)
41
41
42 expected = {
42 expected = {
43 'id': id_,
43 'id': id_,
44 'error': None,
44 'error': None,
45 'result': json.loads(response.body)['result'],
45 'result': json.loads(response.body)['result'],
46 'DEPRECATION_WARNING':
46 'DEPRECATION_WARNING':
47 'DEPRECATED METHOD Please use method `get_ip` instead.'
47 'DEPRECATED METHOD Please use method `get_ip` instead.'
48 }
48 }
49 assert expected == json.loads(response.body)
49 assert expected == json.loads(response.body)
50
50
51 # def test_calls_comment_commit(self, backend, no_notifications):
51 # def test_calls_comment_commit(self, backend, no_notifications):
52 # data = {
52 # data = {
53 # 'repoid': backend.repo_name,
53 # 'repoid': backend.repo_name,
54 # 'status': ChangesetStatus.STATUS_APPROVED,
54 # 'status': ChangesetStatus.STATUS_APPROVED,
55 # 'message': 'Approved',
55 # 'message': 'Approved',
56 # 'revision': 'tip'
56 # 'revision': 'tip'
57 # }
57 # }
58 # with patch.object(repo_api, 'changeset_commit') as comment_mock:
58 # with patch.object(repo_api, 'changeset_commit') as comment_mock:
59 # id_, params = build_data(self.apikey, 'comment_commit', **data)
59 # id_, params = build_data(self.apikey, 'comment_commit', **data)
60 # api_call(self.app, params)
60 # api_call(self.app, params)
61 #
61 #
62 # _, call_args = comment_mock.call_args
62 # _, call_args = comment_mock.call_args
63 # data['commit_id'] = data.pop('revision')
63 # data['commit_id'] = data.pop('revision')
64 # for key in data:
64 # for key in data:
65 # assert call_args[key] == data[key]
65 # assert call_args[key] == data[key]
66
66
67 # def test_warning_log_contains_deprecation_message(self):
67 # def test_warning_log_contains_deprecation_message(self):
68 # api = self.SampleApi()
68 # api = self.SampleApi()
69 # with patch.object(utils, 'log') as log_mock:
69 # with patch.object(utils, 'log') as log_mock:
70 # api.api_method()
70 # api.api_method()
71 #
71 #
72 # assert log_mock.warning.call_count == 1
72 # assert log_mock.warning.call_count == 1
73 # call_args = log_mock.warning.call_args[0]
73 # call_args = log_mock.warning.call_args[0]
74 # assert (
74 # assert (
75 # call_args[0] ==
75 # call_args[0] ==
76 # 'DEPRECATED API CALL on function %s, please use `%s` instead')
76 # 'DEPRECATED API CALL on function %s, please use `%s` instead')
77 # assert call_args[1].__name__ == 'api_method'
77 # assert call_args[1].__name__ == 'api_method'
78 # assert call_args[2] == 'new_method' No newline at end of file
78 # assert call_args[2] == 'new_method'
@@ -1,278 +1,278 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.repo_group import RepoGroupModel
26 from rhodecode.model.repo_group import RepoGroupModel
27 from rhodecode.model.user import UserModel
27 from rhodecode.model.user import UserModel
28 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
28 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
29 from rhodecode.api.tests.utils import (
29 from rhodecode.api.tests.utils import (
30 build_data, api_call, assert_error, assert_ok, crash)
30 build_data, api_call, assert_error, assert_ok, crash)
31 from rhodecode.tests.fixture import Fixture
31 from rhodecode.tests.fixture import Fixture
32
32
33
33
34 fixture = Fixture()
34 fixture = Fixture()
35
35
36
36
37 @pytest.mark.usefixtures("testuser_api", "app")
37 @pytest.mark.usefixtures("testuser_api", "app")
38 class TestApiForkRepo(object):
38 class TestApiForkRepo(object):
39 def test_api_fork_repo(self, backend):
39 def test_api_fork_repo(self, backend):
40 source_name = backend['minimal'].repo_name
40 source_name = backend['minimal'].repo_name
41 fork_name = backend.new_repo_name()
41 fork_name = backend.new_repo_name()
42
42
43 id_, params = build_data(
43 id_, params = build_data(
44 self.apikey, 'fork_repo',
44 self.apikey, 'fork_repo',
45 repoid=source_name,
45 repoid=source_name,
46 fork_name=fork_name,
46 fork_name=fork_name,
47 owner=TEST_USER_ADMIN_LOGIN)
47 owner=TEST_USER_ADMIN_LOGIN)
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = {
50 expected = {
51 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
51 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
52 'success': True,
52 'success': True,
53 'task': None,
53 'task': None,
54 }
54 }
55 try:
55 try:
56 assert_ok(id_, expected, given=response.body)
56 assert_ok(id_, expected, given=response.body)
57 finally:
57 finally:
58 fixture.destroy_repo(fork_name)
58 fixture.destroy_repo(fork_name)
59
59
60 def test_api_fork_repo_into_group(self, backend, user_util):
60 def test_api_fork_repo_into_group(self, backend, user_util):
61 source_name = backend['minimal'].repo_name
61 source_name = backend['minimal'].repo_name
62 repo_group = user_util.create_repo_group()
62 repo_group = user_util.create_repo_group()
63 fork_name = '%s/api-repo-fork' % repo_group.group_name
63 fork_name = '%s/api-repo-fork' % repo_group.group_name
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'fork_repo',
65 self.apikey, 'fork_repo',
66 repoid=source_name,
66 repoid=source_name,
67 fork_name=fork_name,
67 fork_name=fork_name,
68 owner=TEST_USER_ADMIN_LOGIN)
68 owner=TEST_USER_ADMIN_LOGIN)
69 response = api_call(self.app, params)
69 response = api_call(self.app, params)
70
70
71 ret = {
71 ret = {
72 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
72 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
73 'success': True,
73 'success': True,
74 'task': None,
74 'task': None,
75 }
75 }
76 expected = ret
76 expected = ret
77 try:
77 try:
78 assert_ok(id_, expected, given=response.body)
78 assert_ok(id_, expected, given=response.body)
79 finally:
79 finally:
80 fixture.destroy_repo(fork_name)
80 fixture.destroy_repo(fork_name)
81
81
82 def test_api_fork_repo_non_admin(self, backend):
82 def test_api_fork_repo_non_admin(self, backend):
83 source_name = backend['minimal'].repo_name
83 source_name = backend['minimal'].repo_name
84 fork_name = backend.new_repo_name()
84 fork_name = backend.new_repo_name()
85
85
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey_regular, 'fork_repo',
87 self.apikey_regular, 'fork_repo',
88 repoid=source_name,
88 repoid=source_name,
89 fork_name=fork_name)
89 fork_name=fork_name)
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91
91
92 expected = {
92 expected = {
93 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
93 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
94 'success': True,
94 'success': True,
95 'task': None,
95 'task': None,
96 }
96 }
97 try:
97 try:
98 assert_ok(id_, expected, given=response.body)
98 assert_ok(id_, expected, given=response.body)
99 finally:
99 finally:
100 fixture.destroy_repo(fork_name)
100 fixture.destroy_repo(fork_name)
101
101
102 def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util):
102 def test_api_fork_repo_non_admin_into_group_no_permission(self, backend, user_util):
103 source_name = backend['minimal'].repo_name
103 source_name = backend['minimal'].repo_name
104 repo_group = user_util.create_repo_group()
104 repo_group = user_util.create_repo_group()
105 repo_group_name = repo_group.group_name
105 repo_group_name = repo_group.group_name
106 fork_name = '%s/api-repo-fork' % repo_group_name
106 fork_name = '%s/api-repo-fork' % repo_group_name
107
107
108 id_, params = build_data(
108 id_, params = build_data(
109 self.apikey_regular, 'fork_repo',
109 self.apikey_regular, 'fork_repo',
110 repoid=source_name,
110 repoid=source_name,
111 fork_name=fork_name)
111 fork_name=fork_name)
112 response = api_call(self.app, params)
112 response = api_call(self.app, params)
113
113
114 expected = {
114 expected = {
115 'repo_group': 'Repository group `{}` does not exist'.format(
115 'repo_group': 'Repository group `{}` does not exist'.format(
116 repo_group_name)}
116 repo_group_name)}
117 try:
117 try:
118 assert_error(id_, expected, given=response.body)
118 assert_error(id_, expected, given=response.body)
119 finally:
119 finally:
120 fixture.destroy_repo(fork_name)
120 fixture.destroy_repo(fork_name)
121
121
122 def test_api_fork_repo_non_admin_into_group(self, backend, user_util):
122 def test_api_fork_repo_non_admin_into_group(self, backend, user_util):
123 source_name = backend['minimal'].repo_name
123 source_name = backend['minimal'].repo_name
124 repo_group = user_util.create_repo_group()
124 repo_group = user_util.create_repo_group()
125 fork_name = '%s/api-repo-fork' % repo_group.group_name
125 fork_name = '%s/api-repo-fork' % repo_group.group_name
126
126
127 RepoGroupModel().grant_user_permission(
127 RepoGroupModel().grant_user_permission(
128 repo_group, self.TEST_USER_LOGIN, 'group.admin')
128 repo_group, self.TEST_USER_LOGIN, 'group.admin')
129 Session().commit()
129 Session().commit()
130
130
131 id_, params = build_data(
131 id_, params = build_data(
132 self.apikey_regular, 'fork_repo',
132 self.apikey_regular, 'fork_repo',
133 repoid=source_name,
133 repoid=source_name,
134 fork_name=fork_name)
134 fork_name=fork_name)
135 response = api_call(self.app, params)
135 response = api_call(self.app, params)
136
136
137 expected = {
137 expected = {
138 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
138 'msg': 'Created fork of `%s` as `%s`' % (source_name, fork_name),
139 'success': True,
139 'success': True,
140 'task': None,
140 'task': None,
141 }
141 }
142 try:
142 try:
143 assert_ok(id_, expected, given=response.body)
143 assert_ok(id_, expected, given=response.body)
144 finally:
144 finally:
145 fixture.destroy_repo(fork_name)
145 fixture.destroy_repo(fork_name)
146
146
147 def test_api_fork_repo_non_admin_specify_owner(self, backend):
147 def test_api_fork_repo_non_admin_specify_owner(self, backend):
148 source_name = backend['minimal'].repo_name
148 source_name = backend['minimal'].repo_name
149 fork_name = backend.new_repo_name()
149 fork_name = backend.new_repo_name()
150 id_, params = build_data(
150 id_, params = build_data(
151 self.apikey_regular, 'fork_repo',
151 self.apikey_regular, 'fork_repo',
152 repoid=source_name,
152 repoid=source_name,
153 fork_name=fork_name,
153 fork_name=fork_name,
154 owner=TEST_USER_ADMIN_LOGIN)
154 owner=TEST_USER_ADMIN_LOGIN)
155 response = api_call(self.app, params)
155 response = api_call(self.app, params)
156 expected = 'Only RhodeCode super-admin can specify `owner` param'
156 expected = 'Only RhodeCode super-admin can specify `owner` param'
157 assert_error(id_, expected, given=response.body)
157 assert_error(id_, expected, given=response.body)
158
158
159 def test_api_fork_repo_non_admin_no_permission_of_source_repo(
159 def test_api_fork_repo_non_admin_no_permission_of_source_repo(
160 self, backend):
160 self, backend):
161 source_name = backend['minimal'].repo_name
161 source_name = backend['minimal'].repo_name
162 RepoModel().grant_user_permission(repo=source_name,
162 RepoModel().grant_user_permission(repo=source_name,
163 user=self.TEST_USER_LOGIN,
163 user=self.TEST_USER_LOGIN,
164 perm='repository.none')
164 perm='repository.none')
165 fork_name = backend.new_repo_name()
165 fork_name = backend.new_repo_name()
166 id_, params = build_data(
166 id_, params = build_data(
167 self.apikey_regular, 'fork_repo',
167 self.apikey_regular, 'fork_repo',
168 repoid=backend.repo_name,
168 repoid=backend.repo_name,
169 fork_name=fork_name)
169 fork_name=fork_name)
170 response = api_call(self.app, params)
170 response = api_call(self.app, params)
171 expected = 'repository `%s` does not exist' % (backend.repo_name)
171 expected = 'repository `%s` does not exist' % (backend.repo_name)
172 assert_error(id_, expected, given=response.body)
172 assert_error(id_, expected, given=response.body)
173
173
174 def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level(
174 def test_api_fork_repo_non_admin_no_permission_to_fork_to_root_level(
175 self, backend, user_util):
175 self, backend, user_util):
176
176
177 regular_user = user_util.create_user()
177 regular_user = user_util.create_user()
178 regular_user_api_key = regular_user.api_key
178 regular_user_api_key = regular_user.api_key
179 usr = UserModel().get_by_username(regular_user.username)
179 usr = UserModel().get_by_username(regular_user.username)
180 usr.inherit_default_permissions = False
180 usr.inherit_default_permissions = False
181 Session().add(usr)
181 Session().add(usr)
182 UserModel().grant_perm(regular_user.username, 'hg.fork.repository')
182 UserModel().grant_perm(regular_user.username, 'hg.fork.repository')
183
183
184 source_name = backend['minimal'].repo_name
184 source_name = backend['minimal'].repo_name
185 fork_name = backend.new_repo_name()
185 fork_name = backend.new_repo_name()
186 id_, params = build_data(
186 id_, params = build_data(
187 regular_user_api_key, 'fork_repo',
187 regular_user_api_key, 'fork_repo',
188 repoid=source_name,
188 repoid=source_name,
189 fork_name=fork_name)
189 fork_name=fork_name)
190 response = api_call(self.app, params)
190 response = api_call(self.app, params)
191 expected = {
191 expected = {
192 "repo_name": "You do not have the permission to "
192 "repo_name": "You do not have the permission to "
193 "store repositories in the root location."}
193 "store repositories in the root location."}
194 assert_error(id_, expected, given=response.body)
194 assert_error(id_, expected, given=response.body)
195
195
196 def test_api_fork_repo_non_admin_no_permission_to_fork(
196 def test_api_fork_repo_non_admin_no_permission_to_fork(
197 self, backend, user_util):
197 self, backend, user_util):
198
198
199 regular_user = user_util.create_user()
199 regular_user = user_util.create_user()
200 regular_user_api_key = regular_user.api_key
200 regular_user_api_key = regular_user.api_key
201 usr = UserModel().get_by_username(regular_user.username)
201 usr = UserModel().get_by_username(regular_user.username)
202 usr.inherit_default_permissions = False
202 usr.inherit_default_permissions = False
203 Session().add(usr)
203 Session().add(usr)
204
204
205 source_name = backend['minimal'].repo_name
205 source_name = backend['minimal'].repo_name
206 fork_name = backend.new_repo_name()
206 fork_name = backend.new_repo_name()
207 id_, params = build_data(
207 id_, params = build_data(
208 regular_user_api_key, 'fork_repo',
208 regular_user_api_key, 'fork_repo',
209 repoid=source_name,
209 repoid=source_name,
210 fork_name=fork_name)
210 fork_name=fork_name)
211 response = api_call(self.app, params)
211 response = api_call(self.app, params)
212
212
213 expected = "Access was denied to this resource."
213 expected = "Access was denied to this resource."
214 assert_error(id_, expected, given=response.body)
214 assert_error(id_, expected, given=response.body)
215
215
216 def test_api_fork_repo_unknown_owner(self, backend):
216 def test_api_fork_repo_unknown_owner(self, backend):
217 source_name = backend['minimal'].repo_name
217 source_name = backend['minimal'].repo_name
218 fork_name = backend.new_repo_name()
218 fork_name = backend.new_repo_name()
219 owner = 'i-dont-exist'
219 owner = 'i-dont-exist'
220 id_, params = build_data(
220 id_, params = build_data(
221 self.apikey, 'fork_repo',
221 self.apikey, 'fork_repo',
222 repoid=source_name,
222 repoid=source_name,
223 fork_name=fork_name,
223 fork_name=fork_name,
224 owner=owner)
224 owner=owner)
225 response = api_call(self.app, params)
225 response = api_call(self.app, params)
226 expected = 'user `%s` does not exist' % (owner,)
226 expected = 'user `%s` does not exist' % (owner,)
227 assert_error(id_, expected, given=response.body)
227 assert_error(id_, expected, given=response.body)
228
228
229 def test_api_fork_repo_fork_exists(self, backend):
229 def test_api_fork_repo_fork_exists(self, backend):
230 source_name = backend['minimal'].repo_name
230 source_name = backend['minimal'].repo_name
231 fork_name = backend.new_repo_name()
231 fork_name = backend.new_repo_name()
232 fork_repo = fixture.create_fork(source_name, fork_name)
232 fork_repo = fixture.create_fork(source_name, fork_name)
233
233
234 id_, params = build_data(
234 id_, params = build_data(
235 self.apikey, 'fork_repo',
235 self.apikey, 'fork_repo',
236 repoid=source_name,
236 repoid=source_name,
237 fork_name=fork_name,
237 fork_name=fork_name,
238 owner=TEST_USER_ADMIN_LOGIN)
238 owner=TEST_USER_ADMIN_LOGIN)
239 response = api_call(self.app, params)
239 response = api_call(self.app, params)
240
240
241 try:
241 try:
242 expected = {
242 expected = {
243 'unique_repo_name': 'Repository with name `{}` already exists'.format(
243 'unique_repo_name': 'Repository with name `{}` already exists'.format(
244 fork_name)}
244 fork_name)}
245 assert_error(id_, expected, given=response.body)
245 assert_error(id_, expected, given=response.body)
246 finally:
246 finally:
247 fixture.destroy_repo(fork_repo.repo_name)
247 fixture.destroy_repo(fork_repo.repo_name)
248
248
249 def test_api_fork_repo_repo_exists(self, backend):
249 def test_api_fork_repo_repo_exists(self, backend):
250 source_name = backend['minimal'].repo_name
250 source_name = backend['minimal'].repo_name
251 fork_name = source_name
251 fork_name = source_name
252
252
253 id_, params = build_data(
253 id_, params = build_data(
254 self.apikey, 'fork_repo',
254 self.apikey, 'fork_repo',
255 repoid=source_name,
255 repoid=source_name,
256 fork_name=fork_name,
256 fork_name=fork_name,
257 owner=TEST_USER_ADMIN_LOGIN)
257 owner=TEST_USER_ADMIN_LOGIN)
258 response = api_call(self.app, params)
258 response = api_call(self.app, params)
259
259
260 expected = {
260 expected = {
261 'unique_repo_name': 'Repository with name `{}` already exists'.format(
261 'unique_repo_name': 'Repository with name `{}` already exists'.format(
262 fork_name)}
262 fork_name)}
263 assert_error(id_, expected, given=response.body)
263 assert_error(id_, expected, given=response.body)
264
264
265 @mock.patch.object(RepoModel, 'create_fork', crash)
265 @mock.patch.object(RepoModel, 'create_fork', crash)
266 def test_api_fork_repo_exception_occurred(self, backend):
266 def test_api_fork_repo_exception_occurred(self, backend):
267 source_name = backend['minimal'].repo_name
267 source_name = backend['minimal'].repo_name
268 fork_name = backend.new_repo_name()
268 fork_name = backend.new_repo_name()
269 id_, params = build_data(
269 id_, params = build_data(
270 self.apikey, 'fork_repo',
270 self.apikey, 'fork_repo',
271 repoid=source_name,
271 repoid=source_name,
272 fork_name=fork_name,
272 fork_name=fork_name,
273 owner=TEST_USER_ADMIN_LOGIN)
273 owner=TEST_USER_ADMIN_LOGIN)
274 response = api_call(self.app, params)
274 response = api_call(self.app, params)
275
275
276 expected = 'failed to fork repository `%s` as `%s`' % (source_name,
276 expected = 'failed to fork repository `%s` as `%s`' % (source_name,
277 fork_name)
277 fork_name)
278 assert_error(id_, expected, given=response.body)
278 assert_error(id_, expected, given=response.body)
@@ -1,114 +1,114 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21 from rhodecode.tests import HG_REPO
21 from rhodecode.tests import HG_REPO
22 from rhodecode.api.tests.utils import (
22 from rhodecode.api.tests.utils import (
23 build_data, api_call, assert_error, assert_ok)
23 build_data, api_call, assert_error, assert_ok)
24
24
25
25
26 @pytest.mark.usefixtures("testuser_api", "app")
26 @pytest.mark.usefixtures("testuser_api", "app")
27 class TestApiSearch(object):
27 class TestApiSearch(object):
28
28
29 @pytest.mark.parametrize("sort_dir", [
29 @pytest.mark.parametrize("sort_dir", [
30 "asc",
30 "asc",
31 "desc",
31 "desc",
32 ])
32 ])
33 @pytest.mark.parametrize("sort", [
33 @pytest.mark.parametrize("sort", [
34 "xxx",
34 "xxx",
35 "author_email",
35 "author_email",
36 "date",
36 "date",
37 "message",
37 "message",
38 ])
38 ])
39 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
39 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
40 ('todo', 23, [
40 ('todo', 23, [
41 'vcs/backends/hg/inmemory.py',
41 'vcs/backends/hg/inmemory.py',
42 'vcs/tests/test_git.py']),
42 'vcs/tests/test_git.py']),
43 ('extension:rst installation', 6, [
43 ('extension:rst installation', 6, [
44 'docs/index.rst',
44 'docs/index.rst',
45 'docs/installation.rst']),
45 'docs/installation.rst']),
46 ('def repo', 87, [
46 ('def repo', 87, [
47 'vcs/tests/test_git.py',
47 'vcs/tests/test_git.py',
48 'vcs/tests/test_changesets.py']),
48 'vcs/tests/test_changesets.py']),
49 ('repository:%s def test' % HG_REPO, 18, [
49 ('repository:%s def test' % HG_REPO, 18, [
50 'vcs/tests/test_git.py',
50 'vcs/tests/test_git.py',
51 'vcs/tests/test_changesets.py']),
51 'vcs/tests/test_changesets.py']),
52 ('"def main"', 9, [
52 ('"def main"', 9, [
53 'vcs/__init__.py',
53 'vcs/__init__.py',
54 'vcs/tests/__init__.py',
54 'vcs/tests/__init__.py',
55 'vcs/utils/progressbar.py']),
55 'vcs/utils/progressbar.py']),
56 ('owner:test_admin', 358, [
56 ('owner:test_admin', 358, [
57 'vcs/tests/base.py',
57 'vcs/tests/base.py',
58 'MANIFEST.in',
58 'MANIFEST.in',
59 'vcs/utils/termcolors.py',
59 'vcs/utils/termcolors.py',
60 'docs/theme/ADC/static/documentation.png']),
60 'docs/theme/ADC/static/documentation.png']),
61 ('owner:test_admin def main', 72, [
61 ('owner:test_admin def main', 72, [
62 'vcs/__init__.py',
62 'vcs/__init__.py',
63 'vcs/tests/test_utils_filesize.py',
63 'vcs/tests/test_utils_filesize.py',
64 'vcs/tests/test_cli.py']),
64 'vcs/tests/test_cli.py']),
65 ('owner:michał test', 0, []),
65 ('owner:michał test', 0, []),
66 ])
66 ])
67 def test_search_content_results(self, sort_dir, sort, query, expected_hits, expected_paths):
67 def test_search_content_results(self, sort_dir, sort, query, expected_hits, expected_paths):
68 id_, params = build_data(
68 id_, params = build_data(
69 self.apikey_regular, 'search',
69 self.apikey_regular, 'search',
70 search_query=query,
70 search_query=query,
71 search_sort='{}:{}'.format(sort_dir, sort),
71 search_sort='{}:{}'.format(sort_dir, sort),
72 search_type='content')
72 search_type='content')
73
73
74 response = api_call(self.app, params)
74 response = api_call(self.app, params)
75 json_response = response.json
75 json_response = response.json
76
76
77 assert json_response['result']['item_count'] == expected_hits
77 assert json_response['result']['item_count'] == expected_hits
78 paths = [x['f_path'] for x in json_response['result']['results']]
78 paths = [x['f_path'] for x in json_response['result']['results']]
79
79
80 for expected_path in expected_paths:
80 for expected_path in expected_paths:
81 assert expected_path in paths
81 assert expected_path in paths
82
82
83 @pytest.mark.parametrize("sort_dir", [
83 @pytest.mark.parametrize("sort_dir", [
84 "asc",
84 "asc",
85 "desc",
85 "desc",
86 ])
86 ])
87 @pytest.mark.parametrize("sort", [
87 @pytest.mark.parametrize("sort", [
88 "xxx",
88 "xxx",
89 "date",
89 "date",
90 "file",
90 "file",
91 "size",
91 "size",
92 ])
92 ])
93 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
93 @pytest.mark.parametrize("query, expected_hits, expected_paths", [
94 ('readme.rst', 3, []),
94 ('readme.rst', 3, []),
95 ('test*', 75, []),
95 ('test*', 75, []),
96 ('*model*', 1, []),
96 ('*model*', 1, []),
97 ('extension:rst', 48, []),
97 ('extension:rst', 48, []),
98 ('extension:rst api', 24, []),
98 ('extension:rst api', 24, []),
99 ])
99 ])
100 def test_search_file_paths(self, sort_dir, sort, query, expected_hits, expected_paths):
100 def test_search_file_paths(self, sort_dir, sort, query, expected_hits, expected_paths):
101 id_, params = build_data(
101 id_, params = build_data(
102 self.apikey_regular, 'search',
102 self.apikey_regular, 'search',
103 search_query=query,
103 search_query=query,
104 search_sort='{}:{}'.format(sort_dir, sort),
104 search_sort='{}:{}'.format(sort_dir, sort),
105 search_type='path')
105 search_type='path')
106
106
107 response = api_call(self.app, params)
107 response = api_call(self.app, params)
108 json_response = response.json
108 json_response = response.json
109
109
110 assert json_response['result']['item_count'] == expected_hits
110 assert json_response['result']['item_count'] == expected_hits
111 paths = [x['f_path'] for x in json_response['result']['results']]
111 paths = [x['f_path'] for x in json_response['result']['results']]
112
112
113 for expected_path in expected_paths:
113 for expected_path in expected_paths:
114 assert expected_path in paths
114 assert expected_path in paths
@@ -1,101 +1,101 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.lib.str_utils import safe_bytes
23 from rhodecode.lib.str_utils import safe_bytes
24 from rhodecode.model.db import Gist
24 from rhodecode.model.db import Gist
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestApiGetGist(object):
30 class TestApiGetGist(object):
31 def test_api_get_gist(self, gist_util, http_host_only_stub):
31 def test_api_get_gist(self, gist_util, http_host_only_stub):
32 gist = gist_util.create_gist()
32 gist = gist_util.create_gist()
33 gist_id = gist.gist_access_id
33 gist_id = gist.gist_access_id
34 gist_created_on = gist.created_on
34 gist_created_on = gist.created_on
35 gist_modified_at = gist.modified_at
35 gist_modified_at = gist.modified_at
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'get_gist', gistid=gist_id, )
37 self.apikey, 'get_gist', gistid=gist_id, )
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = {
40 expected = {
41 'access_id': gist_id,
41 'access_id': gist_id,
42 'created_on': gist_created_on,
42 'created_on': gist_created_on,
43 'modified_at': gist_modified_at,
43 'modified_at': gist_modified_at,
44 'description': 'new-gist',
44 'description': 'new-gist',
45 'expires': -1.0,
45 'expires': -1.0,
46 'gist_id': int(gist_id),
46 'gist_id': int(gist_id),
47 'type': 'public',
47 'type': 'public',
48 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
48 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
49 'acl_level': Gist.ACL_LEVEL_PUBLIC,
50 'content': None,
50 'content': None,
51 }
51 }
52
52
53 assert_ok(id_, expected, given=response.body)
53 assert_ok(id_, expected, given=response.body)
54
54
55 def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
55 def test_api_get_gist_with_content(self, gist_util, http_host_only_stub):
56 mapping = {
56 mapping = {
57 b'filename1.txt': {'content': b'hello world'},
57 b'filename1.txt': {'content': b'hello world'},
58 safe_bytes('filename1ą.txt'): {'content': safe_bytes('hello worldę')}
58 safe_bytes('filename1ą.txt'): {'content': safe_bytes('hello worldę')}
59 }
59 }
60 gist = gist_util.create_gist(gist_mapping=mapping)
60 gist = gist_util.create_gist(gist_mapping=mapping)
61 gist_id = gist.gist_access_id
61 gist_id = gist.gist_access_id
62 gist_created_on = gist.created_on
62 gist_created_on = gist.created_on
63 gist_modified_at = gist.modified_at
63 gist_modified_at = gist.modified_at
64 id_, params = build_data(
64 id_, params = build_data(
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
65 self.apikey, 'get_gist', gistid=gist_id, content=True)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = {
68 expected = {
69 'access_id': gist_id,
69 'access_id': gist_id,
70 'created_on': gist_created_on,
70 'created_on': gist_created_on,
71 'modified_at': gist_modified_at,
71 'modified_at': gist_modified_at,
72 'description': 'new-gist',
72 'description': 'new-gist',
73 'expires': -1.0,
73 'expires': -1.0,
74 'gist_id': int(gist_id),
74 'gist_id': int(gist_id),
75 'type': 'public',
75 'type': 'public',
76 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
76 'url': 'http://%s/_admin/gists/%s' % (http_host_only_stub, gist_id,),
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
77 'acl_level': Gist.ACL_LEVEL_PUBLIC,
78 'content': {
78 'content': {
79 u'filename1.txt': u'hello world',
79 u'filename1.txt': u'hello world',
80 u'filename1ą.txt': u'hello worldę'
80 u'filename1ą.txt': u'hello worldę'
81 },
81 },
82 }
82 }
83
83
84 assert_ok(id_, expected, given=response.body)
84 assert_ok(id_, expected, given=response.body)
85
85
86 def test_api_get_gist_not_existing(self):
86 def test_api_get_gist_not_existing(self):
87 id_, params = build_data(
87 id_, params = build_data(
88 self.apikey_regular, 'get_gist', gistid='12345', )
88 self.apikey_regular, 'get_gist', gistid='12345', )
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90 expected = 'gist `%s` does not exist' % ('12345',)
90 expected = 'gist `%s` does not exist' % ('12345',)
91 assert_error(id_, expected, given=response.body)
91 assert_error(id_, expected, given=response.body)
92
92
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
93 def test_api_get_gist_private_gist_without_permission(self, gist_util):
94 gist = gist_util.create_gist()
94 gist = gist_util.create_gist()
95 gist_id = gist.gist_access_id
95 gist_id = gist.gist_access_id
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
97 self.apikey_regular, 'get_gist', gistid=gist_id, )
98 response = api_call(self.app, params)
98 response = api_call(self.app, params)
99
99
100 expected = 'gist `%s` does not exist' % (gist_id,)
100 expected = 'gist `%s` does not exist' % (gist_id,)
101 assert_error(id_, expected, given=response.body)
101 assert_error(id_, expected, given=response.body)
@@ -1,73 +1,73 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
23 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error)
25 build_data, api_call, assert_error)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApiGetGist(object):
29 class TestApiGetGist(object):
30 def test_api_get_gists(self, gist_util):
30 def test_api_get_gists(self, gist_util):
31 gist_util.create_gist()
31 gist_util.create_gist()
32 gist_util.create_gist()
32 gist_util.create_gist()
33
33
34 id_, params = build_data(self.apikey, 'get_gists')
34 id_, params = build_data(self.apikey, 'get_gists')
35 response = api_call(self.app, params)
35 response = api_call(self.app, params)
36 assert len(response.json['result']) == 2
36 assert len(response.json['result']) == 2
37
37
38 def test_api_get_gists_regular_user(self, gist_util):
38 def test_api_get_gists_regular_user(self, gist_util):
39 # by admin
39 # by admin
40 gist_util.create_gist()
40 gist_util.create_gist()
41 gist_util.create_gist()
41 gist_util.create_gist()
42
42
43 # by reg user
43 # by reg user
44 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
44 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
45 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
45 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
46 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
46 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
47
47
48 id_, params = build_data(self.apikey_regular, 'get_gists')
48 id_, params = build_data(self.apikey_regular, 'get_gists')
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50 assert len(response.json['result']) == 3
50 assert len(response.json['result']) == 3
51
51
52 def test_api_get_gists_only_for_regular_user(self, gist_util):
52 def test_api_get_gists_only_for_regular_user(self, gist_util):
53 # by admin
53 # by admin
54 gist_util.create_gist()
54 gist_util.create_gist()
55 gist_util.create_gist()
55 gist_util.create_gist()
56
56
57 # by reg user
57 # by reg user
58 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
58 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
59 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
59 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
60 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
60 gist_util.create_gist(owner=self.TEST_USER_LOGIN)
61
61
62 id_, params = build_data(
62 id_, params = build_data(
63 self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN)
63 self.apikey, 'get_gists', userid=self.TEST_USER_LOGIN)
64 response = api_call(self.app, params)
64 response = api_call(self.app, params)
65 assert len(response.json['result']) == 3
65 assert len(response.json['result']) == 3
66
66
67 def test_api_get_gists_regular_user_with_different_userid(self):
67 def test_api_get_gists_regular_user_with_different_userid(self):
68 id_, params = build_data(
68 id_, params = build_data(
69 self.apikey_regular, 'get_gists',
69 self.apikey_regular, 'get_gists',
70 userid=TEST_USER_ADMIN_LOGIN)
70 userid=TEST_USER_ADMIN_LOGIN)
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72 expected = 'userid is not the same as your user'
72 expected = 'userid is not the same as your user'
73 assert_error(id_, expected, given=response.body)
73 assert_error(id_, expected, given=response.body)
@@ -1,35 +1,35 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24
24
25
25
26 @pytest.mark.usefixtures("testuser_api", "app")
26 @pytest.mark.usefixtures("testuser_api", "app")
27 class TestGetIp(object):
27 class TestGetIp(object):
28 def test_api_get_ip(self):
28 def test_api_get_ip(self):
29 id_, params = build_data(self.apikey, 'get_ip')
29 id_, params = build_data(self.apikey, 'get_ip')
30 response = api_call(self.app, params)
30 response = api_call(self.app, params)
31 expected = {
31 expected = {
32 'server_ip_addr': '0.0.0.0',
32 'server_ip_addr': '0.0.0.0',
33 'user_ips': []
33 'user_ips': []
34 }
34 }
35 assert_ok(id_, expected, given=response.body)
35 assert_ok(id_, expected, given=response.body)
@@ -1,90 +1,90 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import Repository, User
23 from rhodecode.model.db import Repository, User
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error)
26 build_data, api_call, assert_ok, assert_error)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGetLocks(object):
30 class TestGetLocks(object):
31 def test_api_get_user_locks_regular_user(self):
31 def test_api_get_user_locks_regular_user(self):
32 id_, params = build_data(self.apikey_regular, 'get_user_locks')
32 id_, params = build_data(self.apikey_regular, 'get_user_locks')
33 response = api_call(self.app, params)
33 response = api_call(self.app, params)
34 expected = []
34 expected = []
35 assert_ok(id_, expected, given=response.body)
35 assert_ok(id_, expected, given=response.body)
36
36
37 def test_api_get_user_locks_with_userid_regular_user(self):
37 def test_api_get_user_locks_with_userid_regular_user(self):
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN)
39 self.apikey_regular, 'get_user_locks', userid=TEST_USER_ADMIN_LOGIN)
40 response = api_call(self.app, params)
40 response = api_call(self.app, params)
41 expected = 'userid is not the same as your user'
41 expected = 'userid is not the same as your user'
42 assert_error(id_, expected, given=response.body)
42 assert_error(id_, expected, given=response.body)
43
43
44 def test_api_get_user_locks(self):
44 def test_api_get_user_locks(self):
45 id_, params = build_data(self.apikey, 'get_user_locks')
45 id_, params = build_data(self.apikey, 'get_user_locks')
46 response = api_call(self.app, params)
46 response = api_call(self.app, params)
47 expected = []
47 expected = []
48 assert_ok(id_, expected, given=response.body)
48 assert_ok(id_, expected, given=response.body)
49
49
50 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
50 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
51 ('apikey', True),
51 ('apikey', True),
52 ('apikey_regular', False),
52 ('apikey_regular', False),
53 ])
53 ])
54 def test_api_get_user_locks_with_one_locked_repo(
54 def test_api_get_user_locks_with_one_locked_repo(
55 self, apikey_attr, expect_secrets, backend):
55 self, apikey_attr, expect_secrets, backend):
56
56
57 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
57 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
58 Repository.lock(
58 Repository.lock(
59 repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
59 repo, User.get_by_username(self.TEST_USER_LOGIN).user_id)
60
60
61 apikey = getattr(self, apikey_attr)
61 apikey = getattr(self, apikey_attr)
62
62
63 id_, params = build_data(apikey, 'get_user_locks')
63 id_, params = build_data(apikey, 'get_user_locks')
64 if apikey_attr == 'apikey':
64 if apikey_attr == 'apikey':
65 # super-admin should call in specific user
65 # super-admin should call in specific user
66 id_, params = build_data(apikey, 'get_user_locks',
66 id_, params = build_data(apikey, 'get_user_locks',
67 userid=self.TEST_USER_LOGIN)
67 userid=self.TEST_USER_LOGIN)
68
68
69 response = api_call(self.app, params)
69 response = api_call(self.app, params)
70 expected = [repo.get_api_data(include_secrets=expect_secrets)]
70 expected = [repo.get_api_data(include_secrets=expect_secrets)]
71 assert_ok(id_, expected, given=response.body)
71 assert_ok(id_, expected, given=response.body)
72
72
73 def test_api_get_user_locks_with_one_locked_repo_for_specific_user(
73 def test_api_get_user_locks_with_one_locked_repo_for_specific_user(
74 self, backend):
74 self, backend):
75 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
75 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
76
76
77 Repository.lock(repo, User.get_by_username(
77 Repository.lock(repo, User.get_by_username(
78 self.TEST_USER_LOGIN).user_id)
78 self.TEST_USER_LOGIN).user_id)
79 id_, params = build_data(
79 id_, params = build_data(
80 self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN)
80 self.apikey, 'get_user_locks', userid=self.TEST_USER_LOGIN)
81 response = api_call(self.app, params)
81 response = api_call(self.app, params)
82 expected = [repo.get_api_data(include_secrets=True)]
82 expected = [repo.get_api_data(include_secrets=True)]
83 assert_ok(id_, expected, given=response.body)
83 assert_ok(id_, expected, given=response.body)
84
84
85 def test_api_get_user_locks_with_userid(self):
85 def test_api_get_user_locks_with_userid(self):
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN)
87 self.apikey, 'get_user_locks', userid=TEST_USER_REGULAR_LOGIN)
88 response = api_call(self.app, params)
88 response = api_call(self.app, params)
89 expected = []
89 expected = []
90 assert_ok(id_, expected, given=response.body)
90 assert_ok(id_, expected, given=response.body)
@@ -1,62 +1,62 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24
24
25
25
26 @pytest.mark.usefixtures("testuser_api", "app")
26 @pytest.mark.usefixtures("testuser_api", "app")
27 class TestGetMethod(object):
27 class TestGetMethod(object):
28 def test_get_methods_no_matches(self):
28 def test_get_methods_no_matches(self):
29 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
29 id_, params = build_data(self.apikey, 'get_method', pattern='hello')
30 response = api_call(self.app, params)
30 response = api_call(self.app, params)
31
31
32 expected = []
32 expected = []
33 assert_ok(id_, expected, given=response.body)
33 assert_ok(id_, expected, given=response.body)
34
34
35 def test_get_methods(self):
35 def test_get_methods(self):
36 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
36 id_, params = build_data(self.apikey, 'get_method', pattern='*comment*')
37 response = api_call(self.app, params)
37 response = api_call(self.app, params)
38
38
39 expected = [
39 expected = [
40 'changeset_comment', 'comment_pull_request', 'get_pull_request_comments',
40 'changeset_comment', 'comment_pull_request', 'get_pull_request_comments',
41 'comment_commit', 'edit_comment', 'get_comment', 'get_repo_comments'
41 'comment_commit', 'edit_comment', 'get_comment', 'get_repo_comments'
42 ]
42 ]
43 assert_ok(id_, expected, given=response.body)
43 assert_ok(id_, expected, given=response.body)
44
44
45 def test_get_methods_on_single_match(self):
45 def test_get_methods_on_single_match(self):
46 id_, params = build_data(self.apikey, 'get_method',
46 id_, params = build_data(self.apikey, 'get_method',
47 pattern='*comment_commit*')
47 pattern='*comment_commit*')
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 expected = ['comment_commit',
50 expected = ['comment_commit',
51 {'apiuser': '<RequiredType>',
51 {'apiuser': '<RequiredType>',
52 'comment_type': "<Optional:'note'>",
52 'comment_type': "<Optional:'note'>",
53 'commit_id': '<RequiredType>',
53 'commit_id': '<RequiredType>',
54 'extra_recipients': '<Optional:[]>',
54 'extra_recipients': '<Optional:[]>',
55 'message': '<RequiredType>',
55 'message': '<RequiredType>',
56 'repoid': '<RequiredType>',
56 'repoid': '<RequiredType>',
57 'request': '<RequiredType>',
57 'request': '<RequiredType>',
58 'resolves_comment_id': '<Optional:None>',
58 'resolves_comment_id': '<Optional:None>',
59 'status': '<Optional:None>',
59 'status': '<Optional:None>',
60 'userid': '<Optional:<OptionalAttr:apiuser>>',
60 'userid': '<Optional:<OptionalAttr:apiuser>>',
61 'send_email': '<Optional:True>'}]
61 'send_email': '<Optional:True>'}]
62 assert_ok(id_, expected, given=response.body)
62 assert_ok(id_, expected, given=response.body)
@@ -1,143 +1,143 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22 import urlobject
22 import urlobject
23
23
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok)
25 build_data, api_call, assert_error, assert_ok)
26 from rhodecode.lib import helpers as h
26 from rhodecode.lib import helpers as h
27 from rhodecode.lib.str_utils import safe_str
27 from rhodecode.lib.str_utils import safe_str
28
28
29
29
30 pytestmark = pytest.mark.backends("git", "hg")
30 pytestmark = pytest.mark.backends("git", "hg")
31
31
32
32
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestGetPullRequest(object):
34 class TestGetPullRequest(object):
35
35
36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
36 def test_api_get_pull_request(self, pr_util, http_host_only_stub):
37 from rhodecode.model.pull_request import PullRequestModel
37 from rhodecode.model.pull_request import PullRequestModel
38 pull_request = pr_util.create_pull_request(mergeable=True)
38 pull_request = pr_util.create_pull_request(mergeable=True)
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'get_pull_request',
40 self.apikey, 'get_pull_request',
41 pullrequestid=pull_request.pull_request_id, merge_state=True)
41 pullrequestid=pull_request.pull_request_id, merge_state=True)
42
42
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44
44
45 assert response.status == '200 OK'
45 assert response.status == '200 OK'
46
46
47 url_obj = urlobject.URLObject(
47 url_obj = urlobject.URLObject(
48 h.route_url(
48 h.route_url(
49 'pullrequest_show',
49 'pullrequest_show',
50 repo_name=pull_request.target_repo.repo_name,
50 repo_name=pull_request.target_repo.repo_name,
51 pull_request_id=pull_request.pull_request_id))
51 pull_request_id=pull_request.pull_request_id))
52
52
53 pr_url = safe_str(
53 pr_url = safe_str(
54 url_obj.with_netloc(http_host_only_stub))
54 url_obj.with_netloc(http_host_only_stub))
55 source_url = safe_str(
55 source_url = safe_str(
56 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
56 pull_request.source_repo.clone_url().with_netloc(http_host_only_stub))
57 target_url = safe_str(
57 target_url = safe_str(
58 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
58 pull_request.target_repo.clone_url().with_netloc(http_host_only_stub))
59 shadow_url = safe_str(
59 shadow_url = safe_str(
60 PullRequestModel().get_shadow_clone_url(pull_request))
60 PullRequestModel().get_shadow_clone_url(pull_request))
61
61
62 expected = {
62 expected = {
63 'pull_request_id': pull_request.pull_request_id,
63 'pull_request_id': pull_request.pull_request_id,
64 'url': pr_url,
64 'url': pr_url,
65 'title': pull_request.title,
65 'title': pull_request.title,
66 'description': pull_request.description,
66 'description': pull_request.description,
67 'status': pull_request.status,
67 'status': pull_request.status,
68 'state': pull_request.pull_request_state,
68 'state': pull_request.pull_request_state,
69 'created_on': pull_request.created_on,
69 'created_on': pull_request.created_on,
70 'updated_on': pull_request.updated_on,
70 'updated_on': pull_request.updated_on,
71 'commit_ids': pull_request.revisions,
71 'commit_ids': pull_request.revisions,
72 'review_status': pull_request.calculated_review_status(),
72 'review_status': pull_request.calculated_review_status(),
73 'mergeable': {
73 'mergeable': {
74 'status': True,
74 'status': True,
75 'message': 'This pull request can be automatically merged.',
75 'message': 'This pull request can be automatically merged.',
76 },
76 },
77 'source': {
77 'source': {
78 'clone_url': source_url,
78 'clone_url': source_url,
79 'repository': pull_request.source_repo.repo_name,
79 'repository': pull_request.source_repo.repo_name,
80 'reference': {
80 'reference': {
81 'name': pull_request.source_ref_parts.name,
81 'name': pull_request.source_ref_parts.name,
82 'type': pull_request.source_ref_parts.type,
82 'type': pull_request.source_ref_parts.type,
83 'commit_id': pull_request.source_ref_parts.commit_id,
83 'commit_id': pull_request.source_ref_parts.commit_id,
84 },
84 },
85 },
85 },
86 'target': {
86 'target': {
87 'clone_url': target_url,
87 'clone_url': target_url,
88 'repository': pull_request.target_repo.repo_name,
88 'repository': pull_request.target_repo.repo_name,
89 'reference': {
89 'reference': {
90 'name': pull_request.target_ref_parts.name,
90 'name': pull_request.target_ref_parts.name,
91 'type': pull_request.target_ref_parts.type,
91 'type': pull_request.target_ref_parts.type,
92 'commit_id': pull_request.target_ref_parts.commit_id,
92 'commit_id': pull_request.target_ref_parts.commit_id,
93 },
93 },
94 },
94 },
95 'merge': {
95 'merge': {
96 'clone_url': shadow_url,
96 'clone_url': shadow_url,
97 'reference': {
97 'reference': {
98 'name': pull_request.shadow_merge_ref.name,
98 'name': pull_request.shadow_merge_ref.name,
99 'type': pull_request.shadow_merge_ref.type,
99 'type': pull_request.shadow_merge_ref.type,
100 'commit_id': pull_request.shadow_merge_ref.commit_id,
100 'commit_id': pull_request.shadow_merge_ref.commit_id,
101 },
101 },
102 },
102 },
103 'author': pull_request.author.get_api_data(include_secrets=False,
103 'author': pull_request.author.get_api_data(include_secrets=False,
104 details='basic'),
104 details='basic'),
105 'reviewers': [
105 'reviewers': [
106 {
106 {
107 'user': reviewer.get_api_data(include_secrets=False,
107 'user': reviewer.get_api_data(include_secrets=False,
108 details='basic'),
108 details='basic'),
109 'reasons': reasons,
109 'reasons': reasons,
110 'review_status': st[0][1].status if st else 'not_reviewed',
110 'review_status': st[0][1].status if st else 'not_reviewed',
111 }
111 }
112 for obj, reviewer, reasons, mandatory, st in
112 for obj, reviewer, reasons, mandatory, st in
113 pull_request.reviewers_statuses()
113 pull_request.reviewers_statuses()
114 ]
114 ]
115 }
115 }
116 assert_ok(id_, expected, response.body)
116 assert_ok(id_, expected, response.body)
117
117
118 def test_api_get_pull_request_repo_error(self, pr_util):
118 def test_api_get_pull_request_repo_error(self, pr_util):
119 pull_request = pr_util.create_pull_request()
119 pull_request = pr_util.create_pull_request()
120 id_, params = build_data(
120 id_, params = build_data(
121 self.apikey, 'get_pull_request',
121 self.apikey, 'get_pull_request',
122 repoid=666, pullrequestid=pull_request.pull_request_id)
122 repoid=666, pullrequestid=pull_request.pull_request_id)
123 response = api_call(self.app, params)
123 response = api_call(self.app, params)
124
124
125 expected = 'repository `666` does not exist'
125 expected = 'repository `666` does not exist'
126 assert_error(id_, expected, given=response.body)
126 assert_error(id_, expected, given=response.body)
127
127
128 def test_api_get_pull_request_pull_request_error(self):
128 def test_api_get_pull_request_pull_request_error(self):
129 id_, params = build_data(
129 id_, params = build_data(
130 self.apikey, 'get_pull_request', pullrequestid=666)
130 self.apikey, 'get_pull_request', pullrequestid=666)
131 response = api_call(self.app, params)
131 response = api_call(self.app, params)
132
132
133 expected = 'pull request `666` does not exist'
133 expected = 'pull request `666` does not exist'
134 assert_error(id_, expected, given=response.body)
134 assert_error(id_, expected, given=response.body)
135
135
136 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
136 def test_api_get_pull_request_pull_request_error_just_pr_id(self):
137 id_, params = build_data(
137 id_, params = build_data(
138 self.apikey, 'get_pull_request',
138 self.apikey, 'get_pull_request',
139 pullrequestid=666)
139 pullrequestid=666)
140 response = api_call(self.app, params)
140 response = api_call(self.app, params)
141
141
142 expected = 'pull request `666` does not exist'
142 expected = 'pull request `666` does not exist'
143 assert_error(id_, expected, given=response.body)
143 assert_error(id_, expected, given=response.body)
@@ -1,83 +1,83 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.tests.utils import (
23 from rhodecode.api.tests.utils import (
24 build_data, api_call, assert_error, assert_ok)
24 build_data, api_call, assert_error, assert_ok)
25
25
26 pytestmark = pytest.mark.backends("git", "hg")
26 pytestmark = pytest.mark.backends("git", "hg")
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGetPullRequestComments(object):
30 class TestGetPullRequestComments(object):
31
31
32 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
32 def test_api_get_pull_request_comments(self, pr_util, http_host_only_stub):
33 from rhodecode.model.pull_request import PullRequestModel
33 from rhodecode.model.pull_request import PullRequestModel
34
34
35 pull_request = pr_util.create_pull_request(mergeable=True)
35 pull_request = pr_util.create_pull_request(mergeable=True)
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'get_pull_request_comments',
37 self.apikey, 'get_pull_request_comments',
38 pullrequestid=pull_request.pull_request_id)
38 pullrequestid=pull_request.pull_request_id)
39
39
40 response = api_call(self.app, params)
40 response = api_call(self.app, params)
41
41
42 assert response.status == '200 OK'
42 assert response.status == '200 OK'
43 resp_date = response.json['result'][0]['comment_created_on']
43 resp_date = response.json['result'][0]['comment_created_on']
44 resp_comment_id = response.json['result'][0]['comment_id']
44 resp_comment_id = response.json['result'][0]['comment_id']
45
45
46 expected = [
46 expected = [
47 {'comment_author': {'active': True,
47 {'comment_author': {'active': True,
48 'full_name_or_username': 'RhodeCode Admin',
48 'full_name_or_username': 'RhodeCode Admin',
49 'username': 'test_admin'},
49 'username': 'test_admin'},
50 'comment_created_on': resp_date,
50 'comment_created_on': resp_date,
51 'comment_f_path': None,
51 'comment_f_path': None,
52 'comment_id': resp_comment_id,
52 'comment_id': resp_comment_id,
53 'comment_lineno': None,
53 'comment_lineno': None,
54 'comment_status': {'status': 'under_review',
54 'comment_status': {'status': 'under_review',
55 'status_lbl': 'Under Review'},
55 'status_lbl': 'Under Review'},
56 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
56 'comment_text': 'Auto status change to |new_status|\n\n.. |new_status| replace:: *"Under Review"*',
57 'comment_type': 'note',
57 'comment_type': 'note',
58 'comment_resolved_by': None,
58 'comment_resolved_by': None,
59 'pull_request_version': None,
59 'pull_request_version': None,
60 'comment_last_version': 0,
60 'comment_last_version': 0,
61 'comment_commit_id': None,
61 'comment_commit_id': None,
62 'comment_pull_request_id': pull_request.pull_request_id
62 'comment_pull_request_id': pull_request.pull_request_id
63 }
63 }
64 ]
64 ]
65 assert_ok(id_, expected, response.body)
65 assert_ok(id_, expected, response.body)
66
66
67 def test_api_get_pull_request_comments_repo_error(self, pr_util):
67 def test_api_get_pull_request_comments_repo_error(self, pr_util):
68 pull_request = pr_util.create_pull_request()
68 pull_request = pr_util.create_pull_request()
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'get_pull_request_comments',
70 self.apikey, 'get_pull_request_comments',
71 repoid=666, pullrequestid=pull_request.pull_request_id)
71 repoid=666, pullrequestid=pull_request.pull_request_id)
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73
73
74 expected = 'repository `666` does not exist'
74 expected = 'repository `666` does not exist'
75 assert_error(id_, expected, given=response.body)
75 assert_error(id_, expected, given=response.body)
76
76
77 def test_api_get_pull_request_comments_pull_request_error(self):
77 def test_api_get_pull_request_comments_pull_request_error(self):
78 id_, params = build_data(
78 id_, params = build_data(
79 self.apikey, 'get_pull_request_comments', pullrequestid=666)
79 self.apikey, 'get_pull_request_comments', pullrequestid=666)
80 response = api_call(self.app, params)
80 response = api_call(self.app, params)
81
81
82 expected = 'pull request `666` does not exist'
82 expected = 'pull request `666` does not exist'
83 assert_error(id_, expected, given=response.body)
83 assert_error(id_, expected, given=response.body)
@@ -1,81 +1,81 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error)
26 build_data, api_call, assert_error)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGetPullRequest(object):
30 class TestGetPullRequest(object):
31
31
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_get_pull_requests(self, pr_util):
33 def test_api_get_pull_requests(self, pr_util):
34 pull_request = pr_util.create_pull_request()
34 pull_request = pr_util.create_pull_request()
35 pull_request_2 = PullRequestModel().create(
35 pull_request_2 = PullRequestModel().create(
36 created_by=pull_request.author,
36 created_by=pull_request.author,
37 source_repo=pull_request.source_repo,
37 source_repo=pull_request.source_repo,
38 source_ref=pull_request.source_ref,
38 source_ref=pull_request.source_ref,
39 target_repo=pull_request.target_repo,
39 target_repo=pull_request.target_repo,
40 target_ref=pull_request.target_ref,
40 target_ref=pull_request.target_ref,
41 revisions=pull_request.revisions,
41 revisions=pull_request.revisions,
42 reviewers=(),
42 reviewers=(),
43 observers=(),
43 observers=(),
44 title=pull_request.title,
44 title=pull_request.title,
45 description=pull_request.description,
45 description=pull_request.description,
46 )
46 )
47 Session().commit()
47 Session().commit()
48 id_, params = build_data(
48 id_, params = build_data(
49 self.apikey, 'get_pull_requests',
49 self.apikey, 'get_pull_requests',
50 repoid=pull_request.target_repo.repo_name)
50 repoid=pull_request.target_repo.repo_name)
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52 assert response.status == '200 OK'
52 assert response.status == '200 OK'
53 assert len(response.json['result']) == 2
53 assert len(response.json['result']) == 2
54
54
55 PullRequestModel().close_pull_request(
55 PullRequestModel().close_pull_request(
56 pull_request_2, pull_request_2.author)
56 pull_request_2, pull_request_2.author)
57 Session().commit()
57 Session().commit()
58
58
59 id_, params = build_data(
59 id_, params = build_data(
60 self.apikey, 'get_pull_requests',
60 self.apikey, 'get_pull_requests',
61 repoid=pull_request.target_repo.repo_name,
61 repoid=pull_request.target_repo.repo_name,
62 status='new')
62 status='new')
63 response = api_call(self.app, params)
63 response = api_call(self.app, params)
64 assert response.status == '200 OK'
64 assert response.status == '200 OK'
65 assert len(response.json['result']) == 1
65 assert len(response.json['result']) == 1
66
66
67 id_, params = build_data(
67 id_, params = build_data(
68 self.apikey, 'get_pull_requests',
68 self.apikey, 'get_pull_requests',
69 repoid=pull_request.target_repo.repo_name,
69 repoid=pull_request.target_repo.repo_name,
70 status='closed')
70 status='closed')
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72 assert response.status == '200 OK'
72 assert response.status == '200 OK'
73 assert len(response.json['result']) == 1
73 assert len(response.json['result']) == 1
74
74
75 @pytest.mark.backends("git", "hg")
75 @pytest.mark.backends("git", "hg")
76 def test_api_get_pull_requests_repo_error(self):
76 def test_api_get_pull_requests_repo_error(self):
77 id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666)
77 id_, params = build_data(self.apikey, 'get_pull_requests', repoid=666)
78 response = api_call(self.app, params)
78 response = api_call(self.app, params)
79
79
80 expected = 'repository `666` does not exist'
80 expected = 'repository `666` does not exist'
81 assert_error(id_, expected, given=response.body)
81 assert_error(id_, expected, given=response.body)
@@ -1,142 +1,142 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error, expected_permissions)
28 build_data, api_call, assert_ok, assert_error, expected_permissions)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestGetRepo(object):
32 class TestGetRepo(object):
33 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
33 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
34 ('apikey', True),
34 ('apikey', True),
35 ('apikey_regular', False),
35 ('apikey_regular', False),
36 ])
36 ])
37 @pytest.mark.parametrize("cache_param", [
37 @pytest.mark.parametrize("cache_param", [
38 True,
38 True,
39 False,
39 False,
40 None,
40 None,
41 ])
41 ])
42 def test_api_get_repo(
42 def test_api_get_repo(
43 self, apikey_attr, expect_secrets, cache_param, backend,
43 self, apikey_attr, expect_secrets, cache_param, backend,
44 user_util):
44 user_util):
45 repo = backend.create_repo()
45 repo = backend.create_repo()
46 repo_id = repo.repo_id
46 repo_id = repo.repo_id
47 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
47 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
48 group = user_util.create_user_group(members=[usr])
48 group = user_util.create_user_group(members=[usr])
49 user_util.grant_user_group_permission_to_repo(
49 user_util.grant_user_group_permission_to_repo(
50 repo=repo, user_group=group, permission_name='repository.read')
50 repo=repo, user_group=group, permission_name='repository.read')
51 Session().commit()
51 Session().commit()
52 kwargs = {
52 kwargs = {
53 'repoid': repo.repo_name,
53 'repoid': repo.repo_name,
54 }
54 }
55 if cache_param is not None:
55 if cache_param is not None:
56 kwargs['cache'] = cache_param
56 kwargs['cache'] = cache_param
57
57
58 apikey = getattr(self, apikey_attr)
58 apikey = getattr(self, apikey_attr)
59 id_, params = build_data(apikey, 'get_repo', **kwargs)
59 id_, params = build_data(apikey, 'get_repo', **kwargs)
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61
61
62 ret = repo.get_api_data()
62 ret = repo.get_api_data()
63
63
64 permissions = expected_permissions(repo)
64 permissions = expected_permissions(repo)
65
65
66 followers = []
66 followers = []
67
67
68 repo = RepoModel().get(repo_id)
68 repo = RepoModel().get(repo_id)
69 for user in repo.followers:
69 for user in repo.followers:
70 followers.append(user.user.get_api_data(
70 followers.append(user.user.get_api_data(
71 include_secrets=expect_secrets))
71 include_secrets=expect_secrets))
72
72
73 ret['permissions'] = permissions
73 ret['permissions'] = permissions
74 ret['followers'] = followers
74 ret['followers'] = followers
75
75
76 expected = ret
76 expected = ret
77
77
78 assert_ok(id_, expected, given=response.body)
78 assert_ok(id_, expected, given=response.body)
79
79
80 @pytest.mark.parametrize("grant_perm", [
80 @pytest.mark.parametrize("grant_perm", [
81 'repository.admin',
81 'repository.admin',
82 'repository.write',
82 'repository.write',
83 'repository.read',
83 'repository.read',
84 ])
84 ])
85 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
85 def test_api_get_repo_by_non_admin(self, grant_perm, backend):
86 # TODO: Depending on which tests are running before this one, we
86 # TODO: Depending on which tests are running before this one, we
87 # start with a different number of permissions in the database.
87 # start with a different number of permissions in the database.
88 repo = RepoModel().get_by_repo_name(backend.repo_name)
88 repo = RepoModel().get_by_repo_name(backend.repo_name)
89 repo_id = repo.repo_id
89 repo_id = repo.repo_id
90 permission_count = len(repo.repo_to_perm)
90 permission_count = len(repo.repo_to_perm)
91
91
92 RepoModel().grant_user_permission(repo=backend.repo_name,
92 RepoModel().grant_user_permission(repo=backend.repo_name,
93 user=self.TEST_USER_LOGIN,
93 user=self.TEST_USER_LOGIN,
94 perm=grant_perm)
94 perm=grant_perm)
95 Session().commit()
95 Session().commit()
96 id_, params = build_data(
96 id_, params = build_data(
97 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
97 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
98 response = api_call(self.app, params)
98 response = api_call(self.app, params)
99
99
100 repo = RepoModel().get_by_repo_name(backend.repo_name)
100 repo = RepoModel().get_by_repo_name(backend.repo_name)
101 ret = repo.get_api_data()
101 ret = repo.get_api_data()
102
102
103 assert permission_count + 1, len(repo.repo_to_perm)
103 assert permission_count + 1, len(repo.repo_to_perm)
104
104
105 permissions = expected_permissions(repo)
105 permissions = expected_permissions(repo)
106
106
107 followers = []
107 followers = []
108
108
109 repo = RepoModel().get(repo_id)
109 repo = RepoModel().get(repo_id)
110 for user in repo.followers:
110 for user in repo.followers:
111 followers.append(user.user.get_api_data())
111 followers.append(user.user.get_api_data())
112
112
113 ret['permissions'] = permissions
113 ret['permissions'] = permissions
114 ret['followers'] = followers
114 ret['followers'] = followers
115
115
116 expected = ret
116 expected = ret
117 try:
117 try:
118 assert_ok(id_, expected, given=response.body)
118 assert_ok(id_, expected, given=response.body)
119 finally:
119 finally:
120 RepoModel().revoke_user_permission(
120 RepoModel().revoke_user_permission(
121 backend.repo_name, self.TEST_USER_LOGIN)
121 backend.repo_name, self.TEST_USER_LOGIN)
122
122
123 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
123 def test_api_get_repo_by_non_admin_no_permission_to_repo(self, backend):
124 RepoModel().grant_user_permission(repo=backend.repo_name,
124 RepoModel().grant_user_permission(repo=backend.repo_name,
125 user=self.TEST_USER_LOGIN,
125 user=self.TEST_USER_LOGIN,
126 perm='repository.none')
126 perm='repository.none')
127
127
128 id_, params = build_data(
128 id_, params = build_data(
129 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
129 self.apikey_regular, 'get_repo', repoid=backend.repo_name)
130 response = api_call(self.app, params)
130 response = api_call(self.app, params)
131
131
132 expected = 'repository `%s` does not exist' % (backend.repo_name)
132 expected = 'repository `%s` does not exist' % (backend.repo_name)
133 assert_error(id_, expected, given=response.body)
133 assert_error(id_, expected, given=response.body)
134
134
135 def test_api_get_repo_not_existing(self):
135 def test_api_get_repo_not_existing(self):
136 id_, params = build_data(
136 id_, params = build_data(
137 self.apikey, 'get_repo', repoid='no-such-repo')
137 self.apikey, 'get_repo', repoid='no-such-repo')
138 response = api_call(self.app, params)
138 response = api_call(self.app, params)
139
139
140 ret = 'repository `%s` does not exist' % 'no-such-repo'
140 ret = 'repository `%s` does not exist' % 'no-such-repo'
141 expected = ret
141 expected = ret
142 assert_error(id_, expected, given=response.body)
142 assert_error(id_, expected, given=response.body)
@@ -1,140 +1,140 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.tests.utils import build_data, api_call, assert_error
23 from rhodecode.api.tests.utils import build_data, api_call, assert_error
24
24
25
25
26 @pytest.mark.usefixtures("testuser_api", "app")
26 @pytest.mark.usefixtures("testuser_api", "app")
27 class TestGetRepoChangeset(object):
27 class TestGetRepoChangeset(object):
28 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
28 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
29 def test_get_repo_changeset(self, details, backend):
29 def test_get_repo_changeset(self, details, backend):
30 commit = backend.repo.get_commit(commit_idx=0)
30 commit = backend.repo.get_commit(commit_idx=0)
31 __, params = build_data(
31 __, params = build_data(
32 self.apikey, 'get_repo_changeset',
32 self.apikey, 'get_repo_changeset',
33 repoid=backend.repo_name, revision=commit.raw_id,
33 repoid=backend.repo_name, revision=commit.raw_id,
34 details=details,
34 details=details,
35 )
35 )
36 response = api_call(self.app, params)
36 response = api_call(self.app, params)
37 result = response.json['result']
37 result = response.json['result']
38 assert result['revision'] == 0
38 assert result['revision'] == 0
39 assert result['raw_id'] == commit.raw_id
39 assert result['raw_id'] == commit.raw_id
40
40
41 if details == 'full':
41 if details == 'full':
42 assert result['refs']['bookmarks'] == getattr(
42 assert result['refs']['bookmarks'] == getattr(
43 commit, 'bookmarks', [])
43 commit, 'bookmarks', [])
44 branches = [commit.branch] if commit.branch else []
44 branches = [commit.branch] if commit.branch else []
45 assert result['refs']['branches'] == branches
45 assert result['refs']['branches'] == branches
46 assert result['refs']['tags'] == commit.tags
46 assert result['refs']['tags'] == commit.tags
47
47
48 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
48 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
49 def test_get_repo_changeset_bad_type(self, details, backend):
49 def test_get_repo_changeset_bad_type(self, details, backend):
50 id_, params = build_data(
50 id_, params = build_data(
51 self.apikey, 'get_repo_changeset',
51 self.apikey, 'get_repo_changeset',
52 repoid=backend.repo_name, revision=0,
52 repoid=backend.repo_name, revision=0,
53 details=details,
53 details=details,
54 )
54 )
55 response = api_call(self.app, params)
55 response = api_call(self.app, params)
56 expected = "commit_id must be a string value got <class 'int'> instead"
56 expected = "commit_id must be a string value got <class 'int'> instead"
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
58
58
59 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
59 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
60 def test_get_repo_changesets(self, details, backend):
60 def test_get_repo_changesets(self, details, backend):
61 limit = 2
61 limit = 2
62 commit = backend.repo.get_commit(commit_idx=0)
62 commit = backend.repo.get_commit(commit_idx=0)
63 __, params = build_data(
63 __, params = build_data(
64 self.apikey, 'get_repo_changesets',
64 self.apikey, 'get_repo_changesets',
65 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
65 repoid=backend.repo_name, start_rev=commit.raw_id, limit=limit,
66 details=details,
66 details=details,
67 )
67 )
68 response = api_call(self.app, params)
68 response = api_call(self.app, params)
69 result = response.json['result']
69 result = response.json['result']
70 assert result
70 assert result
71 assert len(result) == limit
71 assert len(result) == limit
72 for x in range(limit):
72 for x in range(limit):
73 assert result[x]['revision'] == x
73 assert result[x]['revision'] == x
74
74
75 if details == 'full':
75 if details == 'full':
76 for x in range(limit):
76 for x in range(limit):
77 assert 'bookmarks' in result[x]['refs']
77 assert 'bookmarks' in result[x]['refs']
78 assert 'branches' in result[x]['refs']
78 assert 'branches' in result[x]['refs']
79 assert 'tags' in result[x]['refs']
79 assert 'tags' in result[x]['refs']
80
80
81 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
81 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
82 @pytest.mark.parametrize("start_rev, expected_revision", [
82 @pytest.mark.parametrize("start_rev, expected_revision", [
83 ("0", 0),
83 ("0", 0),
84 ("10", 10),
84 ("10", 10),
85 ("20", 20),
85 ("20", 20),
86 ])
86 ])
87 @pytest.mark.backends("hg", "git")
87 @pytest.mark.backends("hg", "git")
88 def test_get_repo_changesets_commit_range(
88 def test_get_repo_changesets_commit_range(
89 self, details, backend, start_rev, expected_revision):
89 self, details, backend, start_rev, expected_revision):
90 limit = 10
90 limit = 10
91 __, params = build_data(
91 __, params = build_data(
92 self.apikey, 'get_repo_changesets',
92 self.apikey, 'get_repo_changesets',
93 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
93 repoid=backend.repo_name, start_rev=start_rev, limit=limit,
94 details=details,
94 details=details,
95 )
95 )
96 response = api_call(self.app, params)
96 response = api_call(self.app, params)
97 result = response.json['result']
97 result = response.json['result']
98 assert result
98 assert result
99 assert len(result) == limit
99 assert len(result) == limit
100 for i in range(limit):
100 for i in range(limit):
101 assert result[i]['revision'] == int(expected_revision) + i
101 assert result[i]['revision'] == int(expected_revision) + i
102
102
103 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
103 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
104 @pytest.mark.parametrize("start_rev, expected_revision", [
104 @pytest.mark.parametrize("start_rev, expected_revision", [
105 ("0", 0),
105 ("0", 0),
106 ("10", 9),
106 ("10", 9),
107 ("20", 19),
107 ("20", 19),
108 ])
108 ])
109 def test_get_repo_changesets_commit_range_svn(
109 def test_get_repo_changesets_commit_range_svn(
110 self, details, backend_svn, start_rev, expected_revision):
110 self, details, backend_svn, start_rev, expected_revision):
111
111
112 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
112 # TODO: johbo: SVN showed a problem here: The parameter "start_rev"
113 # in our API allows to pass in a "Commit ID" as well as a
113 # in our API allows to pass in a "Commit ID" as well as a
114 # "Commit Index". In the case of Subversion it is not possible to
114 # "Commit Index". In the case of Subversion it is not possible to
115 # distinguish these cases. As a workaround we implemented this
115 # distinguish these cases. As a workaround we implemented this
116 # behavior which gives a preference to see it as a "Commit ID".
116 # behavior which gives a preference to see it as a "Commit ID".
117
117
118 limit = 10
118 limit = 10
119 __, params = build_data(
119 __, params = build_data(
120 self.apikey, 'get_repo_changesets',
120 self.apikey, 'get_repo_changesets',
121 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
121 repoid=backend_svn.repo_name, start_rev=start_rev, limit=limit,
122 details=details,
122 details=details,
123 )
123 )
124 response = api_call(self.app, params)
124 response = api_call(self.app, params)
125 result = response.json['result']
125 result = response.json['result']
126 assert result
126 assert result
127 assert len(result) == limit
127 assert len(result) == limit
128 for i in range(limit):
128 for i in range(limit):
129 assert result[i]['revision'] == int(expected_revision) + i
129 assert result[i]['revision'] == int(expected_revision) + i
130
130
131 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
131 @pytest.mark.parametrize("details", ['basic', 'extended', 'full'])
132 def test_get_repo_changesets_bad_type(self, details, backend):
132 def test_get_repo_changesets_bad_type(self, details, backend):
133 id_, params = build_data(
133 id_, params = build_data(
134 self.apikey, 'get_repo_changesets',
134 self.apikey, 'get_repo_changesets',
135 repoid=backend.repo_name, start_rev=0, limit=2,
135 repoid=backend.repo_name, start_rev=0, limit=2,
136 details=details,
136 details=details,
137 )
137 )
138 response = api_call(self.app, params)
138 response = api_call(self.app, params)
139 expected = "commit_id must be a string value got <class 'int'> instead"
139 expected = "commit_id must be a string value got <class 'int'> instead"
140 assert_error(id_, expected, given=response.body)
140 assert_error(id_, expected, given=response.body)
@@ -1,141 +1,141 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import User, ChangesetComment
23 from rhodecode.model.db import User, ChangesetComment
24 from rhodecode.model.meta import Session
24 from rhodecode.model.meta import Session
25 from rhodecode.model.comment import CommentsModel
25 from rhodecode.model.comment import CommentsModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_call_ok)
27 build_data, api_call, assert_error, assert_call_ok)
28
28
29
29
30 @pytest.fixture()
30 @pytest.fixture()
31 def make_repo_comments_factory(request):
31 def make_repo_comments_factory(request):
32
32
33 class Make(object):
33 class Make(object):
34
34
35 def make_comments(self, repo):
35 def make_comments(self, repo):
36 user = User.get_first_super_admin()
36 user = User.get_first_super_admin()
37 commit = repo.scm_instance()[0]
37 commit = repo.scm_instance()[0]
38
38
39 commit_id = commit.raw_id
39 commit_id = commit.raw_id
40 file_0 = commit.affected_files[0]
40 file_0 = commit.affected_files[0]
41 comments = []
41 comments = []
42
42
43 # general
43 # general
44 comment = CommentsModel().create(
44 comment = CommentsModel().create(
45 text='General Comment', repo=repo, user=user, commit_id=commit_id,
45 text='General Comment', repo=repo, user=user, commit_id=commit_id,
46 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
46 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
47 comments.append(comment)
47 comments.append(comment)
48
48
49 # inline
49 # inline
50 comment = CommentsModel().create(
50 comment = CommentsModel().create(
51 text='Inline Comment', repo=repo, user=user, commit_id=commit_id,
51 text='Inline Comment', repo=repo, user=user, commit_id=commit_id,
52 f_path=file_0, line_no='n1',
52 f_path=file_0, line_no='n1',
53 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
53 comment_type=ChangesetComment.COMMENT_TYPE_NOTE, send_email=False)
54 comments.append(comment)
54 comments.append(comment)
55
55
56 # todo
56 # todo
57 comment = CommentsModel().create(
57 comment = CommentsModel().create(
58 text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id,
58 text='INLINE TODO Comment', repo=repo, user=user, commit_id=commit_id,
59 f_path=file_0, line_no='n1',
59 f_path=file_0, line_no='n1',
60 comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False)
60 comment_type=ChangesetComment.COMMENT_TYPE_TODO, send_email=False)
61 comments.append(comment)
61 comments.append(comment)
62
62
63 return comments
63 return comments
64
64
65 return Make()
65 return Make()
66
66
67
67
68 @pytest.mark.usefixtures("testuser_api", "app")
68 @pytest.mark.usefixtures("testuser_api", "app")
69 class TestGetRepo(object):
69 class TestGetRepo(object):
70
70
71 @pytest.mark.parametrize('filters, expected_count', [
71 @pytest.mark.parametrize('filters, expected_count', [
72 ({}, 3),
72 ({}, 3),
73 ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2),
73 ({'comment_type': ChangesetComment.COMMENT_TYPE_NOTE}, 2),
74 ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1),
74 ({'comment_type': ChangesetComment.COMMENT_TYPE_TODO}, 1),
75 ({'commit_id': 'FILLED DYNAMIC'}, 3),
75 ({'commit_id': 'FILLED DYNAMIC'}, 3),
76 ])
76 ])
77 def test_api_get_repo_comments(self, backend, user_util,
77 def test_api_get_repo_comments(self, backend, user_util,
78 make_repo_comments_factory, filters, expected_count):
78 make_repo_comments_factory, filters, expected_count):
79 commits = [{'message': 'A'}, {'message': 'B'}]
79 commits = [{'message': 'A'}, {'message': 'B'}]
80 repo = backend.create_repo(commits=commits)
80 repo = backend.create_repo(commits=commits)
81 make_repo_comments_factory.make_comments(repo)
81 make_repo_comments_factory.make_comments(repo)
82
82
83 api_call_params = {'repoid': repo.repo_name,}
83 api_call_params = {'repoid': repo.repo_name,}
84 api_call_params.update(filters)
84 api_call_params.update(filters)
85
85
86 if 'commit_id' in api_call_params:
86 if 'commit_id' in api_call_params:
87 commit = repo.scm_instance()[0]
87 commit = repo.scm_instance()[0]
88 commit_id = commit.raw_id
88 commit_id = commit.raw_id
89 api_call_params['commit_id'] = commit_id
89 api_call_params['commit_id'] = commit_id
90
90
91 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
91 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
92 response = api_call(self.app, params)
92 response = api_call(self.app, params)
93 result = assert_call_ok(id_, given=response.body)
93 result = assert_call_ok(id_, given=response.body)
94
94
95 assert len(result) == expected_count
95 assert len(result) == expected_count
96
96
97 def test_api_get_repo_comments_wrong_comment_type(
97 def test_api_get_repo_comments_wrong_comment_type(
98 self, make_repo_comments_factory, backend_hg):
98 self, make_repo_comments_factory, backend_hg):
99 commits = [{'message': 'A'}, {'message': 'B'}]
99 commits = [{'message': 'A'}, {'message': 'B'}]
100 repo = backend_hg.create_repo(commits=commits)
100 repo = backend_hg.create_repo(commits=commits)
101 make_repo_comments_factory.make_comments(repo)
101 make_repo_comments_factory.make_comments(repo)
102
102
103 api_call_params = {'repoid': repo.repo_name}
103 api_call_params = {'repoid': repo.repo_name}
104 api_call_params.update({'comment_type': 'bogus'})
104 api_call_params.update({'comment_type': 'bogus'})
105
105
106 expected = 'comment_type must be one of `{}` got {}'.format(
106 expected = 'comment_type must be one of `{}` got {}'.format(
107 ChangesetComment.COMMENT_TYPES, 'bogus')
107 ChangesetComment.COMMENT_TYPES, 'bogus')
108 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
108 id_, params = build_data(self.apikey, 'get_repo_comments', **api_call_params)
109 response = api_call(self.app, params)
109 response = api_call(self.app, params)
110 assert_error(id_, expected, given=response.body)
110 assert_error(id_, expected, given=response.body)
111
111
112 def test_api_get_comment(self, make_repo_comments_factory, backend_hg):
112 def test_api_get_comment(self, make_repo_comments_factory, backend_hg):
113 commits = [{'message': 'A'}, {'message': 'B'}]
113 commits = [{'message': 'A'}, {'message': 'B'}]
114 repo = backend_hg.create_repo(commits=commits)
114 repo = backend_hg.create_repo(commits=commits)
115
115
116 comments = make_repo_comments_factory.make_comments(repo)
116 comments = make_repo_comments_factory.make_comments(repo)
117 comment_ids = [x.comment_id for x in comments]
117 comment_ids = [x.comment_id for x in comments]
118 Session().commit()
118 Session().commit()
119
119
120 for comment_id in comment_ids:
120 for comment_id in comment_ids:
121 id_, params = build_data(self.apikey, 'get_comment',
121 id_, params = build_data(self.apikey, 'get_comment',
122 **{'comment_id': comment_id})
122 **{'comment_id': comment_id})
123 response = api_call(self.app, params)
123 response = api_call(self.app, params)
124 result = assert_call_ok(id_, given=response.body)
124 result = assert_call_ok(id_, given=response.body)
125 assert result['comment_id'] == comment_id
125 assert result['comment_id'] == comment_id
126
126
127 def test_api_get_comment_no_access(self, make_repo_comments_factory, backend_hg, user_util):
127 def test_api_get_comment_no_access(self, make_repo_comments_factory, backend_hg, user_util):
128 commits = [{'message': 'A'}, {'message': 'B'}]
128 commits = [{'message': 'A'}, {'message': 'B'}]
129 repo = backend_hg.create_repo(commits=commits)
129 repo = backend_hg.create_repo(commits=commits)
130 comments = make_repo_comments_factory.make_comments(repo)
130 comments = make_repo_comments_factory.make_comments(repo)
131 comment_id = comments[0].comment_id
131 comment_id = comments[0].comment_id
132
132
133 test_user = user_util.create_user()
133 test_user = user_util.create_user()
134 user_util.grant_user_permission_to_repo(repo, test_user, 'repository.none')
134 user_util.grant_user_permission_to_repo(repo, test_user, 'repository.none')
135
135
136 id_, params = build_data(test_user.api_key, 'get_comment',
136 id_, params = build_data(test_user.api_key, 'get_comment',
137 **{'comment_id': comment_id})
137 **{'comment_id': comment_id})
138 response = api_call(self.app, params)
138 response = api_call(self.app, params)
139 assert_error(id_,
139 assert_error(id_,
140 expected='comment `{}` does not exist'.format(comment_id),
140 expected='comment `{}` does not exist'.format(comment_id),
141 given=response.body)
141 given=response.body)
@@ -1,54 +1,54 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo_group import RepoGroupModel
23 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error, expected_permissions)
25 build_data, api_call, assert_ok, assert_error, expected_permissions)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestApiGetRepoGroup(object):
29 class TestApiGetRepoGroup(object):
30 def test_api_get_repo_group(self, user_util):
30 def test_api_get_repo_group(self, user_util):
31 repo_group = user_util.create_repo_group()
31 repo_group = user_util.create_repo_group()
32 repo_group_name = repo_group.group_name
32 repo_group_name = repo_group.group_name
33
33
34 id_, params = build_data(
34 id_, params = build_data(
35 self.apikey, 'get_repo_group', repogroupid=repo_group_name)
35 self.apikey, 'get_repo_group', repogroupid=repo_group_name)
36 response = api_call(self.app, params)
36 response = api_call(self.app, params)
37
37
38 repo_group = RepoGroupModel()._get_repo_group(repo_group_name)
38 repo_group = RepoGroupModel()._get_repo_group(repo_group_name)
39 ret = repo_group.get_api_data()
39 ret = repo_group.get_api_data()
40
40
41 permissions = expected_permissions(repo_group)
41 permissions = expected_permissions(repo_group)
42
42
43 ret['permissions'] = permissions
43 ret['permissions'] = permissions
44 expected = ret
44 expected = ret
45 assert_ok(id_, expected, given=response.body)
45 assert_ok(id_, expected, given=response.body)
46
46
47 def test_api_get_repo_group_not_existing(self):
47 def test_api_get_repo_group_not_existing(self):
48 id_, params = build_data(
48 id_, params = build_data(
49 self.apikey, 'get_repo_group', repogroupid='no-such-repo-group')
49 self.apikey, 'get_repo_group', repogroupid='no-such-repo-group')
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 ret = 'repository group `%s` does not exist' % 'no-such-repo-group'
52 ret = 'repository group `%s` does not exist' % 'no-such-repo-group'
53 expected = ret
53 expected = ret
54 assert_error(id_, expected, given=response.body)
54 assert_error(id_, expected, given=response.body)
@@ -1,39 +1,39 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo_group import RepoGroupModel
23 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, jsonify
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestApiGetRepoGroups(object):
28 class TestApiGetRepoGroups(object):
29 def test_api_get_repo_groups(self):
29 def test_api_get_repo_groups(self):
30 id_, params = build_data(self.apikey, 'get_repo_groups')
30 id_, params = build_data(self.apikey, 'get_repo_groups')
31 response = api_call(self.app, params)
31 response = api_call(self.app, params)
32
32
33 result = []
33 result = []
34 for repo in RepoGroupModel().get_all():
34 for repo in RepoGroupModel().get_all():
35 result.append(repo.get_api_data())
35 result.append(repo.get_api_data())
36 ret = jsonify(result)
36 ret = jsonify(result)
37
37
38 expected = ret
38 expected = ret
39 assert_ok(id_, expected, given=response.body)
39 assert_ok(id_, expected, given=response.body)
@@ -1,141 +1,141 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGetRepoNodes(object):
30 class TestGetRepoNodes(object):
31 @pytest.mark.parametrize("name, ret_type", [
31 @pytest.mark.parametrize("name, ret_type", [
32 ('all', 'all'),
32 ('all', 'all'),
33 ('dirs', 'dirs'),
33 ('dirs', 'dirs'),
34 ('files', 'files'),
34 ('files', 'files'),
35 ])
35 ])
36 def test_api_get_repo_nodes(self, name, ret_type, backend):
36 def test_api_get_repo_nodes(self, name, ret_type, backend):
37 commit_id = 'tip'
37 commit_id = 'tip'
38 path = '/'
38 path = '/'
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'get_repo_nodes',
40 self.apikey, 'get_repo_nodes',
41 repoid=backend.repo_name, revision=commit_id,
41 repoid=backend.repo_name, revision=commit_id,
42 root_path=path,
42 root_path=path,
43 ret_type=ret_type)
43 ret_type=ret_type)
44 response = api_call(self.app, params)
44 response = api_call(self.app, params)
45
45
46 # we don't the actual return types here since it's tested somewhere
46 # we don't the actual return types here since it's tested somewhere
47 # else
47 # else
48 expected = response.json['result']
48 expected = response.json['result']
49 assert_ok(id_, expected, given=response.body)
49 assert_ok(id_, expected, given=response.body)
50
50
51 def test_api_get_repo_nodes_bad_commits(self, backend):
51 def test_api_get_repo_nodes_bad_commits(self, backend):
52 commit_id = 'i-dont-exist'
52 commit_id = 'i-dont-exist'
53 path = '/'
53 path = '/'
54 id_, params = build_data(
54 id_, params = build_data(
55 self.apikey, 'get_repo_nodes',
55 self.apikey, 'get_repo_nodes',
56 repoid=backend.repo_name, revision=commit_id,
56 repoid=backend.repo_name, revision=commit_id,
57 root_path=path, )
57 root_path=path, )
58 response = api_call(self.app, params)
58 response = api_call(self.app, params)
59
59
60 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
60 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
61 assert_error(id_, expected, given=response.body)
61 assert_error(id_, expected, given=response.body)
62
62
63 def test_api_get_repo_nodes_bad_path(self, backend):
63 def test_api_get_repo_nodes_bad_path(self, backend):
64 commit_id = 'tip'
64 commit_id = 'tip'
65 path = '/idontexits'
65 path = '/idontexits'
66 id_, params = build_data(
66 id_, params = build_data(
67 self.apikey, 'get_repo_nodes',
67 self.apikey, 'get_repo_nodes',
68 repoid=backend.repo_name, revision=commit_id,
68 repoid=backend.repo_name, revision=commit_id,
69 root_path=path, )
69 root_path=path, )
70 response = api_call(self.app, params)
70 response = api_call(self.app, params)
71
71
72 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
72 expected = 'failed to get repo: `%s` nodes' % (backend.repo_name,)
73 assert_error(id_, expected, given=response.body)
73 assert_error(id_, expected, given=response.body)
74
74
75 def test_api_get_repo_nodes_max_file_bytes(self, backend):
75 def test_api_get_repo_nodes_max_file_bytes(self, backend):
76 commit_id = 'tip'
76 commit_id = 'tip'
77 path = '/'
77 path = '/'
78 max_file_bytes = 500
78 max_file_bytes = 500
79
79
80 id_, params = build_data(
80 id_, params = build_data(
81 self.apikey, 'get_repo_nodes',
81 self.apikey, 'get_repo_nodes',
82 repoid=backend.repo_name, revision=commit_id, details='full',
82 repoid=backend.repo_name, revision=commit_id, details='full',
83 root_path=path)
83 root_path=path)
84 response = api_call(self.app, params)
84 response = api_call(self.app, params)
85 assert any(file['content'] and len(file['content']) > max_file_bytes
85 assert any(file['content'] and len(file['content']) > max_file_bytes
86 for file in response.json['result'])
86 for file in response.json['result'])
87
87
88 id_, params = build_data(
88 id_, params = build_data(
89 self.apikey, 'get_repo_nodes',
89 self.apikey, 'get_repo_nodes',
90 repoid=backend.repo_name, revision=commit_id,
90 repoid=backend.repo_name, revision=commit_id,
91 root_path=path, details='full',
91 root_path=path, details='full',
92 max_file_bytes=max_file_bytes)
92 max_file_bytes=max_file_bytes)
93 response = api_call(self.app, params)
93 response = api_call(self.app, params)
94 assert all(
94 assert all(
95 file['content'] is None if file['size'] > max_file_bytes else True
95 file['content'] is None if file['size'] > max_file_bytes else True
96 for file in response.json['result'])
96 for file in response.json['result'])
97
97
98 def test_api_get_repo_nodes_bad_ret_type(self, backend):
98 def test_api_get_repo_nodes_bad_ret_type(self, backend):
99 commit_id = 'tip'
99 commit_id = 'tip'
100 path = '/'
100 path = '/'
101 ret_type = 'error'
101 ret_type = 'error'
102 id_, params = build_data(
102 id_, params = build_data(
103 self.apikey, 'get_repo_nodes',
103 self.apikey, 'get_repo_nodes',
104 repoid=backend.repo_name, revision=commit_id,
104 repoid=backend.repo_name, revision=commit_id,
105 root_path=path,
105 root_path=path,
106 ret_type=ret_type)
106 ret_type=ret_type)
107 response = api_call(self.app, params)
107 response = api_call(self.app, params)
108
108
109 expected = ('ret_type must be one of %s'
109 expected = ('ret_type must be one of %s'
110 % (','.join(['all', 'dirs', 'files'])))
110 % (','.join(['all', 'dirs', 'files'])))
111 assert_error(id_, expected, given=response.body)
111 assert_error(id_, expected, given=response.body)
112
112
113 @pytest.mark.parametrize("name, ret_type, grant_perm", [
113 @pytest.mark.parametrize("name, ret_type, grant_perm", [
114 ('all', 'all', 'repository.write'),
114 ('all', 'all', 'repository.write'),
115 ('dirs', 'dirs', 'repository.admin'),
115 ('dirs', 'dirs', 'repository.admin'),
116 ('files', 'files', 'repository.read'),
116 ('files', 'files', 'repository.read'),
117 ])
117 ])
118 def test_api_get_repo_nodes_by_regular_user(
118 def test_api_get_repo_nodes_by_regular_user(
119 self, name, ret_type, grant_perm, backend):
119 self, name, ret_type, grant_perm, backend):
120 RepoModel().grant_user_permission(repo=backend.repo_name,
120 RepoModel().grant_user_permission(repo=backend.repo_name,
121 user=self.TEST_USER_LOGIN,
121 user=self.TEST_USER_LOGIN,
122 perm=grant_perm)
122 perm=grant_perm)
123 Session().commit()
123 Session().commit()
124
124
125 commit_id = 'tip'
125 commit_id = 'tip'
126 path = '/'
126 path = '/'
127 id_, params = build_data(
127 id_, params = build_data(
128 self.apikey_regular, 'get_repo_nodes',
128 self.apikey_regular, 'get_repo_nodes',
129 repoid=backend.repo_name, revision=commit_id,
129 repoid=backend.repo_name, revision=commit_id,
130 root_path=path,
130 root_path=path,
131 ret_type=ret_type)
131 ret_type=ret_type)
132 response = api_call(self.app, params)
132 response = api_call(self.app, params)
133
133
134 # we don't the actual return types here since it's tested somewhere
134 # we don't the actual return types here since it's tested somewhere
135 # else
135 # else
136 expected = response.json['result']
136 expected = response.json['result']
137 try:
137 try:
138 assert_ok(id_, expected, given=response.body)
138 assert_ok(id_, expected, given=response.body)
139 finally:
139 finally:
140 RepoModel().revoke_user_permission(
140 RepoModel().revoke_user_permission(
141 backend.repo_name, self.TEST_USER_LOGIN)
141 backend.repo_name, self.TEST_USER_LOGIN)
@@ -1,39 +1,39 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.repo import RepoModel
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
27 from rhodecode.api.tests.utils import (
27 from rhodecode.api.tests.utils import (
28 build_data, api_call, assert_ok, assert_error, expected_permissions)
28 build_data, api_call, assert_ok, assert_error, expected_permissions)
29
29
30
30
31 @pytest.mark.usefixtures("testuser_api", "app")
31 @pytest.mark.usefixtures("testuser_api", "app")
32 class TestGetRepo(object):
32 class TestGetRepo(object):
33 def test_api_get_repo_refs(self, backend, user_util):
33 def test_api_get_repo_refs(self, backend, user_util):
34 repo = backend.create_repo()
34 repo = backend.create_repo()
35 id_, params = build_data(self.apikey, 'get_repo_refs',
35 id_, params = build_data(self.apikey, 'get_repo_refs',
36 **{'repoid': repo.repo_name,})
36 **{'repoid': repo.repo_name,})
37 response = api_call(self.app, params)
37 response = api_call(self.app, params)
38 expected = repo.scm_instance().refs()
38 expected = repo.scm_instance().refs()
39 assert_ok(id_, expected, given=response.body)
39 assert_ok(id_, expected, given=response.body)
@@ -1,128 +1,128 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo import RepoModel
23 from rhodecode.model.repo import RepoModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error, jsonify)
25 build_data, api_call, assert_ok, assert_error, jsonify)
26 from rhodecode.model.db import User
26 from rhodecode.model.db import User
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGetRepos(object):
30 class TestGetRepos(object):
31 def test_api_get_repos(self):
31 def test_api_get_repos(self):
32 id_, params = build_data(self.apikey, 'get_repos')
32 id_, params = build_data(self.apikey, 'get_repos')
33 response = api_call(self.app, params)
33 response = api_call(self.app, params)
34
34
35 result = []
35 result = []
36 for repo in RepoModel().get_all():
36 for repo in RepoModel().get_all():
37 result.append(repo.get_api_data(include_secrets=True))
37 result.append(repo.get_api_data(include_secrets=True))
38 ret = jsonify(result)
38 ret = jsonify(result)
39
39
40 expected = ret
40 expected = ret
41 assert_ok(id_, expected, given=response.body)
41 assert_ok(id_, expected, given=response.body)
42
42
43 def test_api_get_repos_only_toplevel(self, user_util):
43 def test_api_get_repos_only_toplevel(self, user_util):
44 repo_group = user_util.create_repo_group(auto_cleanup=True)
44 repo_group = user_util.create_repo_group(auto_cleanup=True)
45 user_util.create_repo(parent=repo_group)
45 user_util.create_repo(parent=repo_group)
46
46
47 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
47 id_, params = build_data(self.apikey, 'get_repos', traverse=0)
48 response = api_call(self.app, params)
48 response = api_call(self.app, params)
49
49
50 result = []
50 result = []
51 for repo in RepoModel().get_repos_for_root(root=None):
51 for repo in RepoModel().get_repos_for_root(root=None):
52 result.append(repo.get_api_data(include_secrets=True))
52 result.append(repo.get_api_data(include_secrets=True))
53 expected = jsonify(result)
53 expected = jsonify(result)
54
54
55 assert_ok(id_, expected, given=response.body)
55 assert_ok(id_, expected, given=response.body)
56
56
57 def test_api_get_repos_with_wrong_root(self):
57 def test_api_get_repos_with_wrong_root(self):
58 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
58 id_, params = build_data(self.apikey, 'get_repos', root='abracadabra')
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 expected = 'Root repository group `abracadabra` does not exist'
61 expected = 'Root repository group `abracadabra` does not exist'
62 assert_error(id_, expected, given=response.body)
62 assert_error(id_, expected, given=response.body)
63
63
64 def test_api_get_repos_with_root(self, user_util):
64 def test_api_get_repos_with_root(self, user_util):
65 repo_group = user_util.create_repo_group(auto_cleanup=True)
65 repo_group = user_util.create_repo_group(auto_cleanup=True)
66 repo_group_name = repo_group.group_name
66 repo_group_name = repo_group.group_name
67
67
68 user_util.create_repo(parent=repo_group)
68 user_util.create_repo(parent=repo_group)
69 user_util.create_repo(parent=repo_group)
69 user_util.create_repo(parent=repo_group)
70
70
71 # nested, should not show up
71 # nested, should not show up
72 user_util._test_name = '{}/'.format(repo_group_name)
72 user_util._test_name = '{}/'.format(repo_group_name)
73 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
73 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
74 user_util.create_repo(parent=sub_repo_group)
74 user_util.create_repo(parent=sub_repo_group)
75
75
76 id_, params = build_data(self.apikey, 'get_repos',
76 id_, params = build_data(self.apikey, 'get_repos',
77 root=repo_group_name, traverse=0)
77 root=repo_group_name, traverse=0)
78 response = api_call(self.app, params)
78 response = api_call(self.app, params)
79
79
80 result = []
80 result = []
81 for repo in RepoModel().get_repos_for_root(repo_group):
81 for repo in RepoModel().get_repos_for_root(repo_group):
82 result.append(repo.get_api_data(include_secrets=True))
82 result.append(repo.get_api_data(include_secrets=True))
83
83
84 assert len(result) == 2
84 assert len(result) == 2
85 expected = jsonify(result)
85 expected = jsonify(result)
86 assert_ok(id_, expected, given=response.body)
86 assert_ok(id_, expected, given=response.body)
87
87
88 def test_api_get_repos_with_root_and_traverse(self, user_util):
88 def test_api_get_repos_with_root_and_traverse(self, user_util):
89 repo_group = user_util.create_repo_group(auto_cleanup=True)
89 repo_group = user_util.create_repo_group(auto_cleanup=True)
90 repo_group_name = repo_group.group_name
90 repo_group_name = repo_group.group_name
91
91
92 user_util.create_repo(parent=repo_group)
92 user_util.create_repo(parent=repo_group)
93 user_util.create_repo(parent=repo_group)
93 user_util.create_repo(parent=repo_group)
94
94
95 # nested, should not show up
95 # nested, should not show up
96 user_util._test_name = '{}/'.format(repo_group_name)
96 user_util._test_name = '{}/'.format(repo_group_name)
97 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
97 sub_repo_group = user_util.create_repo_group(auto_cleanup=True)
98 user_util.create_repo(parent=sub_repo_group)
98 user_util.create_repo(parent=sub_repo_group)
99
99
100 id_, params = build_data(self.apikey, 'get_repos',
100 id_, params = build_data(self.apikey, 'get_repos',
101 root=repo_group_name, traverse=1)
101 root=repo_group_name, traverse=1)
102 response = api_call(self.app, params)
102 response = api_call(self.app, params)
103
103
104 result = []
104 result = []
105 for repo in RepoModel().get_repos_for_root(
105 for repo in RepoModel().get_repos_for_root(
106 repo_group_name, traverse=True):
106 repo_group_name, traverse=True):
107 result.append(repo.get_api_data(include_secrets=True))
107 result.append(repo.get_api_data(include_secrets=True))
108
108
109 assert len(result) == 3
109 assert len(result) == 3
110 expected = jsonify(result)
110 expected = jsonify(result)
111 assert_ok(id_, expected, given=response.body)
111 assert_ok(id_, expected, given=response.body)
112
112
113 def test_api_get_repos_non_admin(self):
113 def test_api_get_repos_non_admin(self):
114 id_, params = build_data(self.apikey_regular, 'get_repos')
114 id_, params = build_data(self.apikey_regular, 'get_repos')
115 response = api_call(self.app, params)
115 response = api_call(self.app, params)
116
116
117 user = User.get_by_username(self.TEST_USER_LOGIN)
117 user = User.get_by_username(self.TEST_USER_LOGIN)
118 allowed_repos = user.AuthUser().permissions['repositories']
118 allowed_repos = user.AuthUser().permissions['repositories']
119
119
120 result = []
120 result = []
121 for repo in RepoModel().get_all():
121 for repo in RepoModel().get_all():
122 perm = allowed_repos[repo.repo_name]
122 perm = allowed_repos[repo.repo_name]
123 if perm in ['repository.read', 'repository.write', 'repository.admin']:
123 if perm in ['repository.read', 'repository.write', 'repository.admin']:
124 result.append(repo.get_api_data())
124 result.append(repo.get_api_data())
125 ret = jsonify(result)
125 ret = jsonify(result)
126
126
127 expected = ret
127 expected = ret
128 assert_ok(id_, expected, given=response.body)
128 assert_ok(id_, expected, given=response.body)
@@ -1,83 +1,83 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.scm import ScmModel
23 from rhodecode.model.scm import ScmModel
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
25
25
26
26
27 @pytest.fixture()
27 @pytest.fixture()
28 def http_host_stub():
28 def http_host_stub():
29 """
29 """
30 To ensure that we can get an IP address, this test shall run with a
30 To ensure that we can get an IP address, this test shall run with a
31 hostname set to "localhost".
31 hostname set to "localhost".
32 """
32 """
33 return 'localhost:80'
33 return 'localhost:80'
34
34
35
35
36 @pytest.mark.usefixtures("testuser_api", "app")
36 @pytest.mark.usefixtures("testuser_api", "app")
37 class TestGetServerInfo(object):
37 class TestGetServerInfo(object):
38 def test_api_get_server_info(self):
38 def test_api_get_server_info(self):
39 id_, params = build_data(self.apikey, 'get_server_info')
39 id_, params = build_data(self.apikey, 'get_server_info')
40 response = api_call(self.app, params)
40 response = api_call(self.app, params)
41 resp = response.json
41 resp = response.json
42 expected = ScmModel().get_server_info()
42 expected = ScmModel().get_server_info()
43 expected['memory'] = resp['result']['memory']
43 expected['memory'] = resp['result']['memory']
44 expected['uptime'] = resp['result']['uptime']
44 expected['uptime'] = resp['result']['uptime']
45 expected['load'] = resp['result']['load']
45 expected['load'] = resp['result']['load']
46 expected['cpu'] = resp['result']['cpu']
46 expected['cpu'] = resp['result']['cpu']
47 expected['storage'] = resp['result']['storage']
47 expected['storage'] = resp['result']['storage']
48 expected['storage_temp'] = resp['result']['storage_temp']
48 expected['storage_temp'] = resp['result']['storage_temp']
49 expected['storage_inodes'] = resp['result']['storage_inodes']
49 expected['storage_inodes'] = resp['result']['storage_inodes']
50 expected['server'] = resp['result']['server']
50 expected['server'] = resp['result']['server']
51
51
52 expected['index_storage'] = resp['result']['index_storage']
52 expected['index_storage'] = resp['result']['index_storage']
53 expected['storage'] = resp['result']['storage']
53 expected['storage'] = resp['result']['storage']
54
54
55 assert_ok(id_, expected, given=response.body)
55 assert_ok(id_, expected, given=response.body)
56
56
57 def test_api_get_server_info_ip(self):
57 def test_api_get_server_info_ip(self):
58 id_, params = build_data(self.apikey, 'get_server_info')
58 id_, params = build_data(self.apikey, 'get_server_info')
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60 resp = response.json
60 resp = response.json
61 expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'})
61 expected = ScmModel().get_server_info({'SERVER_NAME': 'unknown'})
62 expected['memory'] = resp['result']['memory']
62 expected['memory'] = resp['result']['memory']
63 expected['uptime'] = resp['result']['uptime']
63 expected['uptime'] = resp['result']['uptime']
64 expected['load'] = resp['result']['load']
64 expected['load'] = resp['result']['load']
65 expected['cpu'] = resp['result']['cpu']
65 expected['cpu'] = resp['result']['cpu']
66 expected['storage'] = resp['result']['storage']
66 expected['storage'] = resp['result']['storage']
67 expected['storage_temp'] = resp['result']['storage_temp']
67 expected['storage_temp'] = resp['result']['storage_temp']
68 expected['storage_inodes'] = resp['result']['storage_inodes']
68 expected['storage_inodes'] = resp['result']['storage_inodes']
69 expected['server'] = resp['result']['server']
69 expected['server'] = resp['result']['server']
70
70
71 expected['index_storage'] = resp['result']['index_storage']
71 expected['index_storage'] = resp['result']['index_storage']
72 expected['storage'] = resp['result']['storage']
72 expected['storage'] = resp['result']['storage']
73
73
74 assert_ok(id_, expected, given=response.body)
74 assert_ok(id_, expected, given=response.body)
75
75
76 def test_api_get_server_info_data_for_search_index_build(self):
76 def test_api_get_server_info_data_for_search_index_build(self):
77 id_, params = build_data(self.apikey, 'get_server_info')
77 id_, params = build_data(self.apikey, 'get_server_info')
78 response = api_call(self.app, params)
78 response = api_call(self.app, params)
79 resp = response.json
79 resp = response.json
80
80
81 # required by indexer
81 # required by indexer
82 assert resp['result']['index_storage']
82 assert resp['result']['index_storage']
83 assert resp['result']['storage']
83 assert resp['result']['storage']
@@ -1,85 +1,85 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.lib.auth import AuthUser
22 from rhodecode.lib.auth import AuthUser
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error)
26 build_data, api_call, assert_ok, assert_error)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGetUser(object):
30 class TestGetUser(object):
31 def test_api_get_user(self):
31 def test_api_get_user(self):
32 id_, params = build_data(
32 id_, params = build_data(
33 self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN)
33 self.apikey, 'get_user', userid=TEST_USER_ADMIN_LOGIN)
34 response = api_call(self.app, params)
34 response = api_call(self.app, params)
35
35
36 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
36 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
37 ret = usr.get_api_data(include_secrets=True)
37 ret = usr.get_api_data(include_secrets=True)
38 permissions = AuthUser(usr.user_id).permissions
38 permissions = AuthUser(usr.user_id).permissions
39 ret['permissions'] = permissions
39 ret['permissions'] = permissions
40 ret['permissions_summary'] = permissions
40 ret['permissions_summary'] = permissions
41
41
42 expected = ret
42 expected = ret
43 assert_ok(id_, expected, given=response.body)
43 assert_ok(id_, expected, given=response.body)
44
44
45 def test_api_get_user_not_existing(self):
45 def test_api_get_user_not_existing(self):
46 id_, params = build_data(self.apikey, 'get_user', userid='trololo')
46 id_, params = build_data(self.apikey, 'get_user', userid='trololo')
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48
48
49 expected = "user `%s` does not exist" % 'trololo'
49 expected = "user `%s` does not exist" % 'trololo'
50 assert_error(id_, expected, given=response.body)
50 assert_error(id_, expected, given=response.body)
51
51
52 def test_api_get_user_without_giving_userid(self):
52 def test_api_get_user_without_giving_userid(self):
53 id_, params = build_data(self.apikey, 'get_user')
53 id_, params = build_data(self.apikey, 'get_user')
54 response = api_call(self.app, params)
54 response = api_call(self.app, params)
55
55
56 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
56 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
57 ret = usr.get_api_data(include_secrets=True)
57 ret = usr.get_api_data(include_secrets=True)
58 permissions = AuthUser(usr.user_id).permissions
58 permissions = AuthUser(usr.user_id).permissions
59 ret['permissions'] = permissions
59 ret['permissions'] = permissions
60 ret['permissions_summary'] = permissions
60 ret['permissions_summary'] = permissions
61
61
62 expected = ret
62 expected = ret
63 assert_ok(id_, expected, given=response.body)
63 assert_ok(id_, expected, given=response.body)
64
64
65 def test_api_get_user_without_giving_userid_non_admin(self):
65 def test_api_get_user_without_giving_userid_non_admin(self):
66 id_, params = build_data(self.apikey_regular, 'get_user')
66 id_, params = build_data(self.apikey_regular, 'get_user')
67 response = api_call(self.app, params)
67 response = api_call(self.app, params)
68
68
69 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
69 usr = UserModel().get_by_username(self.TEST_USER_LOGIN)
70 ret = usr.get_api_data(include_secrets=True)
70 ret = usr.get_api_data(include_secrets=True)
71 permissions = AuthUser(usr.user_id).permissions
71 permissions = AuthUser(usr.user_id).permissions
72 ret['permissions'] = permissions
72 ret['permissions'] = permissions
73 ret['permissions_summary'] = permissions
73 ret['permissions_summary'] = permissions
74
74
75 expected = ret
75 expected = ret
76 assert_ok(id_, expected, given=response.body)
76 assert_ok(id_, expected, given=response.body)
77
77
78 def test_api_get_user_with_giving_userid_non_admin(self):
78 def test_api_get_user_with_giving_userid_non_admin(self):
79 id_, params = build_data(
79 id_, params = build_data(
80 self.apikey_regular, 'get_user',
80 self.apikey_regular, 'get_user',
81 userid=self.TEST_USER_LOGIN)
81 userid=self.TEST_USER_LOGIN)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83
83
84 expected = 'userid is not the same as your user'
84 expected = 'userid is not the same as your user'
85 assert_error(id_, expected, given=response.body)
85 assert_error(id_, expected, given=response.body)
@@ -1,75 +1,75 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.user import UserModel
22 from rhodecode.model.user import UserModel
23 from rhodecode.api.tests.utils import (
23 from rhodecode.api.tests.utils import (
24 build_data, api_call, assert_ok, assert_error, expected_permissions)
24 build_data, api_call, assert_ok, assert_error, expected_permissions)
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetUserGroups(object):
28 class TestGetUserGroups(object):
29 def test_api_get_user_group(self, user_util):
29 def test_api_get_user_group(self, user_util):
30 user, group = user_util.create_user_with_group()
30 user, group = user_util.create_user_with_group()
31 id_, params = build_data(
31 id_, params = build_data(
32 self.apikey, 'get_user_group', usergroupid=group.users_group_name)
32 self.apikey, 'get_user_group', usergroupid=group.users_group_name)
33 response = api_call(self.app, params)
33 response = api_call(self.app, params)
34
34
35 ret = group.get_api_data()
35 ret = group.get_api_data()
36 ret['users'] = [user.get_api_data()]
36 ret['users'] = [user.get_api_data()]
37
37
38 permissions = expected_permissions(group)
38 permissions = expected_permissions(group)
39
39
40 ret['permissions'] = permissions
40 ret['permissions'] = permissions
41 ret['permissions_summary'] = response.json['result']['permissions_summary']
41 ret['permissions_summary'] = response.json['result']['permissions_summary']
42 expected = ret
42 expected = ret
43 assert_ok(id_, expected, given=response.body)
43 assert_ok(id_, expected, given=response.body)
44
44
45 def test_api_get_user_group_regular_user(self, user_util):
45 def test_api_get_user_group_regular_user(self, user_util):
46 user, group = user_util.create_user_with_group()
46 user, group = user_util.create_user_with_group()
47 id_, params = build_data(
47 id_, params = build_data(
48 self.apikey_regular, 'get_user_group',
48 self.apikey_regular, 'get_user_group',
49 usergroupid=group.users_group_name)
49 usergroupid=group.users_group_name)
50 response = api_call(self.app, params)
50 response = api_call(self.app, params)
51
51
52 ret = group.get_api_data()
52 ret = group.get_api_data()
53 ret['users'] = [user.get_api_data()]
53 ret['users'] = [user.get_api_data()]
54
54
55 permissions = expected_permissions(group)
55 permissions = expected_permissions(group)
56
56
57 ret['permissions'] = permissions
57 ret['permissions'] = permissions
58 ret['permissions_summary'] = response.json['result']['permissions_summary']
58 ret['permissions_summary'] = response.json['result']['permissions_summary']
59 expected = ret
59 expected = ret
60 assert_ok(id_, expected, given=response.body)
60 assert_ok(id_, expected, given=response.body)
61
61
62 def test_api_get_user_group_regular_user_permission_denied(
62 def test_api_get_user_group_regular_user_permission_denied(
63 self, user_util):
63 self, user_util):
64 group = user_util.create_user_group()
64 group = user_util.create_user_group()
65 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
65 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
66 group_name = group.users_group_name
66 group_name = group.users_group_name
67 user_util.grant_user_permission_to_user_group(
67 user_util.grant_user_permission_to_user_group(
68 group, user, 'usergroup.none')
68 group, user, 'usergroup.none')
69
69
70 id_, params = build_data(
70 id_, params = build_data(
71 self.apikey_regular, 'get_user_group', usergroupid=group_name)
71 self.apikey_regular, 'get_user_group', usergroupid=group_name)
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73
73
74 expected = 'user group `%s` does not exist' % (group_name,)
74 expected = 'user group `%s` does not exist' % (group_name,)
75 assert_error(id_, expected, given=response.body)
75 assert_error(id_, expected, given=response.body)
@@ -1,69 +1,69 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.api.tests.utils import build_data, api_call
24 from rhodecode.api.tests.utils import build_data, api_call
25 from rhodecode.lib.ext_json import json
25 from rhodecode.lib.ext_json import json
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestGetUserGroups(object):
29 class TestGetUserGroups(object):
30 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
30 @pytest.mark.parametrize("apikey_attr, expect_secrets", [
31 ('apikey', True),
31 ('apikey', True),
32 ('apikey_regular', False),
32 ('apikey_regular', False),
33 ])
33 ])
34 def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util):
34 def test_api_get_user_groups(self, apikey_attr, expect_secrets, user_util):
35 first_group = user_util.create_user_group()
35 first_group = user_util.create_user_group()
36 second_group = user_util.create_user_group()
36 second_group = user_util.create_user_group()
37 expected = [
37 expected = [
38 g.get_api_data(include_secrets=expect_secrets)
38 g.get_api_data(include_secrets=expect_secrets)
39 for g in (first_group, second_group)]
39 for g in (first_group, second_group)]
40
40
41 apikey = getattr(self, apikey_attr)
41 apikey = getattr(self, apikey_attr)
42 id_, params = build_data(apikey, 'get_user_groups', )
42 id_, params = build_data(apikey, 'get_user_groups', )
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44 self._assert_ok(id_, expected, response)
44 self._assert_ok(id_, expected, response)
45
45
46 def test_api_get_user_groups_regular_user(self, user_util):
46 def test_api_get_user_groups_regular_user(self, user_util):
47 first_group = user_util.create_user_group()
47 first_group = user_util.create_user_group()
48 second_group = user_util.create_user_group()
48 second_group = user_util.create_user_group()
49 expected = [g.get_api_data() for g in (first_group, second_group)]
49 expected = [g.get_api_data() for g in (first_group, second_group)]
50
50
51 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
51 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53 self._assert_ok(id_, expected, response)
53 self._assert_ok(id_, expected, response)
54
54
55 def test_api_get_user_groups_regular_user_no_permission(self, user_util):
55 def test_api_get_user_groups_regular_user_no_permission(self, user_util):
56 group = user_util.create_user_group()
56 group = user_util.create_user_group()
57 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
57 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
58 user_util.grant_user_permission_to_user_group(
58 user_util.grant_user_permission_to_user_group(
59 group, user, 'usergroup.none')
59 group, user, 'usergroup.none')
60 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
60 id_, params = build_data(self.apikey_regular, 'get_user_groups', )
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62 expected = []
62 expected = []
63 self._assert_ok(id_, expected, response)
63 self._assert_ok(id_, expected, response)
64
64
65 def _assert_ok(self, id_, expected_list, response):
65 def _assert_ok(self, id_, expected_list, response):
66 result = json.loads(response.body)
66 result = json.loads(response.body)
67 assert result['id'] == id_
67 assert result['id'] == id_
68 assert result['error'] is None
68 assert result['error'] is None
69 assert result['result'] == expected_list
69 assert result['result'] == expected_list
@@ -1,39 +1,39 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.db import User
22 from rhodecode.model.db import User
23 from rhodecode.api.tests.utils import (
23 from rhodecode.api.tests.utils import (
24 build_data, api_call, assert_ok, jsonify)
24 build_data, api_call, assert_ok, jsonify)
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGetUsers(object):
28 class TestGetUsers(object):
29 def test_api_get_users(self):
29 def test_api_get_users(self):
30 id_, params = build_data(self.apikey, 'get_users', )
30 id_, params = build_data(self.apikey, 'get_users', )
31 response = api_call(self.app, params)
31 response = api_call(self.app, params)
32 ret_all = []
32 ret_all = []
33 _users = User.query().filter(User.username != User.DEFAULT_USER) \
33 _users = User.query().filter(User.username != User.DEFAULT_USER) \
34 .order_by(User.username).all()
34 .order_by(User.username).all()
35 for usr in _users:
35 for usr in _users:
36 ret = usr.get_api_data(include_secrets=True)
36 ret = usr.get_api_data(include_secrets=True)
37 ret_all.append(jsonify(ret))
37 ret_all.append(jsonify(ret))
38 expected = ret_all
38 expected = ret_all
39 assert_ok(id_, expected, given=response.body)
39 assert_ok(id_, expected, given=response.body)
@@ -1,89 +1,89 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo import RepoModel
23 from rhodecode.model.repo import RepoModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestGrantUserGroupPermission(object):
29 class TestGrantUserGroupPermission(object):
30 @pytest.mark.parametrize("name, perm", [
30 @pytest.mark.parametrize("name, perm", [
31 ('none', 'repository.none'),
31 ('none', 'repository.none'),
32 ('read', 'repository.read'),
32 ('read', 'repository.read'),
33 ('write', 'repository.write'),
33 ('write', 'repository.write'),
34 ('admin', 'repository.admin')
34 ('admin', 'repository.admin')
35 ])
35 ])
36 def test_api_grant_user_group_permission(
36 def test_api_grant_user_group_permission(
37 self, name, perm, backend, user_util):
37 self, name, perm, backend, user_util):
38 user_group = user_util.create_user_group()
38 user_group = user_util.create_user_group()
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey,
40 self.apikey,
41 'grant_user_group_permission',
41 'grant_user_group_permission',
42 repoid=backend.repo_name,
42 repoid=backend.repo_name,
43 usergroupid=user_group.users_group_name,
43 usergroupid=user_group.users_group_name,
44 perm=perm)
44 perm=perm)
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 ret = {
47 ret = {
48 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
48 'msg': 'Granted perm: `%s` for user group: `%s` in repo: `%s`' % (
49 perm, user_group.users_group_name, backend.repo_name
49 perm, user_group.users_group_name, backend.repo_name
50 ),
50 ),
51 'success': True
51 'success': True
52 }
52 }
53 expected = ret
53 expected = ret
54 assert_ok(id_, expected, given=response.body)
54 assert_ok(id_, expected, given=response.body)
55
55
56 def test_api_grant_user_group_permission_wrong_permission(
56 def test_api_grant_user_group_permission_wrong_permission(
57 self, backend, user_util):
57 self, backend, user_util):
58 perm = 'haha.no.permission'
58 perm = 'haha.no.permission'
59 user_group = user_util.create_user_group()
59 user_group = user_util.create_user_group()
60 id_, params = build_data(
60 id_, params = build_data(
61 self.apikey,
61 self.apikey,
62 'grant_user_group_permission',
62 'grant_user_group_permission',
63 repoid=backend.repo_name,
63 repoid=backend.repo_name,
64 usergroupid=user_group.users_group_name,
64 usergroupid=user_group.users_group_name,
65 perm=perm)
65 perm=perm)
66 response = api_call(self.app, params)
66 response = api_call(self.app, params)
67
67
68 expected = 'permission `%s` does not exist.' % (perm,)
68 expected = 'permission `%s` does not exist.' % (perm,)
69 assert_error(id_, expected, given=response.body)
69 assert_error(id_, expected, given=response.body)
70
70
71 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
71 @mock.patch.object(RepoModel, 'grant_user_group_permission', crash)
72 def test_api_grant_user_group_permission_exception_when_adding(
72 def test_api_grant_user_group_permission_exception_when_adding(
73 self, backend, user_util):
73 self, backend, user_util):
74 perm = 'repository.read'
74 perm = 'repository.read'
75 user_group = user_util.create_user_group()
75 user_group = user_util.create_user_group()
76 id_, params = build_data(
76 id_, params = build_data(
77 self.apikey,
77 self.apikey,
78 'grant_user_group_permission',
78 'grant_user_group_permission',
79 repoid=backend.repo_name,
79 repoid=backend.repo_name,
80 usergroupid=user_group.users_group_name,
80 usergroupid=user_group.users_group_name,
81 perm=perm)
81 perm=perm)
82 response = api_call(self.app, params)
82 response = api_call(self.app, params)
83
83
84 expected = (
84 expected = (
85 'failed to edit permission for user group: `%s` in repo: `%s`' % (
85 'failed to edit permission for user group: `%s` in repo: `%s`' % (
86 user_group.users_group_name, backend.repo_name
86 user_group.users_group_name, backend.repo_name
87 )
87 )
88 )
88 )
89 assert_error(id_, expected, given=response.body)
89 assert_error(id_, expected, given=response.body)
@@ -1,172 +1,172 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGrantUserGroupPermissionFromRepoGroup(object):
30 class TestGrantUserGroupPermissionFromRepoGroup(object):
31 @pytest.mark.parametrize("name, perm, apply_to_children", [
31 @pytest.mark.parametrize("name, perm, apply_to_children", [
32 ('none', 'group.none', 'none'),
32 ('none', 'group.none', 'none'),
33 ('read', 'group.read', 'none'),
33 ('read', 'group.read', 'none'),
34 ('write', 'group.write', 'none'),
34 ('write', 'group.write', 'none'),
35 ('admin', 'group.admin', 'none'),
35 ('admin', 'group.admin', 'none'),
36
36
37 ('none', 'group.none', 'all'),
37 ('none', 'group.none', 'all'),
38 ('read', 'group.read', 'all'),
38 ('read', 'group.read', 'all'),
39 ('write', 'group.write', 'all'),
39 ('write', 'group.write', 'all'),
40 ('admin', 'group.admin', 'all'),
40 ('admin', 'group.admin', 'all'),
41
41
42 ('none', 'group.none', 'repos'),
42 ('none', 'group.none', 'repos'),
43 ('read', 'group.read', 'repos'),
43 ('read', 'group.read', 'repos'),
44 ('write', 'group.write', 'repos'),
44 ('write', 'group.write', 'repos'),
45 ('admin', 'group.admin', 'repos'),
45 ('admin', 'group.admin', 'repos'),
46
46
47 ('none', 'group.none', 'groups'),
47 ('none', 'group.none', 'groups'),
48 ('read', 'group.read', 'groups'),
48 ('read', 'group.read', 'groups'),
49 ('write', 'group.write', 'groups'),
49 ('write', 'group.write', 'groups'),
50 ('admin', 'group.admin', 'groups'),
50 ('admin', 'group.admin', 'groups'),
51 ])
51 ])
52 def test_api_grant_user_group_permission_to_repo_group(
52 def test_api_grant_user_group_permission_to_repo_group(
53 self, name, perm, apply_to_children, user_util):
53 self, name, perm, apply_to_children, user_util):
54 user_group = user_util.create_user_group()
54 user_group = user_util.create_user_group()
55 repo_group = user_util.create_repo_group()
55 repo_group = user_util.create_repo_group()
56 user_util.create_repo(parent=repo_group)
56 user_util.create_repo(parent=repo_group)
57
57
58 id_, params = build_data(
58 id_, params = build_data(
59 self.apikey,
59 self.apikey,
60 'grant_user_group_permission_to_repo_group',
60 'grant_user_group_permission_to_repo_group',
61 repogroupid=repo_group.name,
61 repogroupid=repo_group.name,
62 usergroupid=user_group.users_group_name,
62 usergroupid=user_group.users_group_name,
63 perm=perm,
63 perm=perm,
64 apply_to_children=apply_to_children,)
64 apply_to_children=apply_to_children,)
65 response = api_call(self.app, params)
65 response = api_call(self.app, params)
66
66
67 ret = {
67 ret = {
68 'msg': (
68 'msg': (
69 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
69 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
70 ' in repo group: `%s`' % (
70 ' in repo group: `%s`' % (
71 perm, apply_to_children, user_group.users_group_name,
71 perm, apply_to_children, user_group.users_group_name,
72 repo_group.name
72 repo_group.name
73 )
73 )
74 ),
74 ),
75 'success': True
75 'success': True
76 }
76 }
77 expected = ret
77 expected = ret
78 try:
78 try:
79 assert_ok(id_, expected, given=response.body)
79 assert_ok(id_, expected, given=response.body)
80 finally:
80 finally:
81 RepoGroupModel().revoke_user_group_permission(
81 RepoGroupModel().revoke_user_group_permission(
82 repo_group.group_id, user_group.users_group_id)
82 repo_group.group_id, user_group.users_group_id)
83
83
84 @pytest.mark.parametrize(
84 @pytest.mark.parametrize(
85 "name, perm, apply_to_children, grant_admin, access_ok", [
85 "name, perm, apply_to_children, grant_admin, access_ok", [
86 ('none_fails', 'group.none', 'none', False, False),
86 ('none_fails', 'group.none', 'none', False, False),
87 ('read_fails', 'group.read', 'none', False, False),
87 ('read_fails', 'group.read', 'none', False, False),
88 ('write_fails', 'group.write', 'none', False, False),
88 ('write_fails', 'group.write', 'none', False, False),
89 ('admin_fails', 'group.admin', 'none', False, False),
89 ('admin_fails', 'group.admin', 'none', False, False),
90
90
91 # with granted perms
91 # with granted perms
92 ('none_ok', 'group.none', 'none', True, True),
92 ('none_ok', 'group.none', 'none', True, True),
93 ('read_ok', 'group.read', 'none', True, True),
93 ('read_ok', 'group.read', 'none', True, True),
94 ('write_ok', 'group.write', 'none', True, True),
94 ('write_ok', 'group.write', 'none', True, True),
95 ('admin_ok', 'group.admin', 'none', True, True),
95 ('admin_ok', 'group.admin', 'none', True, True),
96 ]
96 ]
97 )
97 )
98 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
98 def test_api_grant_user_group_permission_to_repo_group_by_regular_user(
99 self, name, perm, apply_to_children, grant_admin, access_ok,
99 self, name, perm, apply_to_children, grant_admin, access_ok,
100 user_util):
100 user_util):
101 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
101 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
102 user_group = user_util.create_user_group()
102 user_group = user_util.create_user_group()
103 repo_group = user_util.create_repo_group()
103 repo_group = user_util.create_repo_group()
104 if grant_admin:
104 if grant_admin:
105 user_util.grant_user_permission_to_repo_group(
105 user_util.grant_user_permission_to_repo_group(
106 repo_group, user, 'group.admin')
106 repo_group, user, 'group.admin')
107
107
108 id_, params = build_data(
108 id_, params = build_data(
109 self.apikey_regular,
109 self.apikey_regular,
110 'grant_user_group_permission_to_repo_group',
110 'grant_user_group_permission_to_repo_group',
111 repogroupid=repo_group.name,
111 repogroupid=repo_group.name,
112 usergroupid=user_group.users_group_name,
112 usergroupid=user_group.users_group_name,
113 perm=perm,
113 perm=perm,
114 apply_to_children=apply_to_children,)
114 apply_to_children=apply_to_children,)
115 response = api_call(self.app, params)
115 response = api_call(self.app, params)
116 if access_ok:
116 if access_ok:
117 ret = {
117 ret = {
118 'msg': (
118 'msg': (
119 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
119 'Granted perm: `%s` (recursive:%s) for user group: `%s`'
120 ' in repo group: `%s`' % (
120 ' in repo group: `%s`' % (
121 perm, apply_to_children, user_group.users_group_name,
121 perm, apply_to_children, user_group.users_group_name,
122 repo_group.name
122 repo_group.name
123 )
123 )
124 ),
124 ),
125 'success': True
125 'success': True
126 }
126 }
127 expected = ret
127 expected = ret
128 try:
128 try:
129 assert_ok(id_, expected, given=response.body)
129 assert_ok(id_, expected, given=response.body)
130 finally:
130 finally:
131 RepoGroupModel().revoke_user_group_permission(
131 RepoGroupModel().revoke_user_group_permission(
132 repo_group.group_id, user_group.users_group_id)
132 repo_group.group_id, user_group.users_group_id)
133 else:
133 else:
134 expected = 'repository group `%s` does not exist' % (repo_group.name,)
134 expected = 'repository group `%s` does not exist' % (repo_group.name,)
135 assert_error(id_, expected, given=response.body)
135 assert_error(id_, expected, given=response.body)
136
136
137 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
137 def test_api_grant_user_group_permission_to_repo_group_wrong_permission(
138 self, user_util):
138 self, user_util):
139 user_group = user_util.create_user_group()
139 user_group = user_util.create_user_group()
140 repo_group = user_util.create_repo_group()
140 repo_group = user_util.create_repo_group()
141 perm = 'haha.no.permission'
141 perm = 'haha.no.permission'
142 id_, params = build_data(
142 id_, params = build_data(
143 self.apikey,
143 self.apikey,
144 'grant_user_group_permission_to_repo_group',
144 'grant_user_group_permission_to_repo_group',
145 repogroupid=repo_group.name,
145 repogroupid=repo_group.name,
146 usergroupid=user_group.users_group_name,
146 usergroupid=user_group.users_group_name,
147 perm=perm)
147 perm=perm)
148 response = api_call(self.app, params)
148 response = api_call(self.app, params)
149
149
150 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,)
150 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,)
151 assert_error(id_, expected, given=response.body)
151 assert_error(id_, expected, given=response.body)
152
152
153 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
153 @mock.patch.object(RepoGroupModel, 'grant_user_group_permission', crash)
154 def test_api_grant_user_group_permission_exception_when_adding_2(
154 def test_api_grant_user_group_permission_exception_when_adding_2(
155 self, user_util):
155 self, user_util):
156 user_group = user_util.create_user_group()
156 user_group = user_util.create_user_group()
157 repo_group = user_util.create_repo_group()
157 repo_group = user_util.create_repo_group()
158 perm = 'group.read'
158 perm = 'group.read'
159 id_, params = build_data(
159 id_, params = build_data(
160 self.apikey,
160 self.apikey,
161 'grant_user_group_permission_to_repo_group',
161 'grant_user_group_permission_to_repo_group',
162 repogroupid=repo_group.name,
162 repogroupid=repo_group.name,
163 usergroupid=user_group.users_group_name,
163 usergroupid=user_group.users_group_name,
164 perm=perm)
164 perm=perm)
165 response = api_call(self.app, params)
165 response = api_call(self.app, params)
166
166
167 expected = (
167 expected = (
168 'failed to edit permission for user group: `%s`'
168 'failed to edit permission for user group: `%s`'
169 ' in repo group: `%s`' % (
169 ' in repo group: `%s`' % (
170 user_group.users_group_name, repo_group.name)
170 user_group.users_group_name, repo_group.name)
171 )
171 )
172 assert_error(id_, expected, given=response.body)
172 assert_error(id_, expected, given=response.body)
@@ -1,96 +1,96 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.user_group import UserGroupModel
22 from rhodecode.model.user_group import UserGroupModel
23 from rhodecode.api.tests.utils import (
23 from rhodecode.api.tests.utils import (
24 build_data, api_call, assert_ok, assert_error)
24 build_data, api_call, assert_ok, assert_error)
25
25
26
26
27 @pytest.mark.usefixtures("testuser_api", "app")
27 @pytest.mark.usefixtures("testuser_api", "app")
28 class TestGrantUserGroupPermissionFromUserGroup(object):
28 class TestGrantUserGroupPermissionFromUserGroup(object):
29 @pytest.mark.parametrize("name, perm", [
29 @pytest.mark.parametrize("name, perm", [
30 ('none', 'usergroup.none'),
30 ('none', 'usergroup.none'),
31 ('read', 'usergroup.read'),
31 ('read', 'usergroup.read'),
32 ('write', 'usergroup.write'),
32 ('write', 'usergroup.write'),
33 ('admin', 'usergroup.admin'),
33 ('admin', 'usergroup.admin'),
34
34
35 ('none', 'usergroup.none'),
35 ('none', 'usergroup.none'),
36 ('read', 'usergroup.read'),
36 ('read', 'usergroup.read'),
37 ('write', 'usergroup.write'),
37 ('write', 'usergroup.write'),
38 ('admin', 'usergroup.admin'),
38 ('admin', 'usergroup.admin'),
39
39
40 ('none', 'usergroup.none'),
40 ('none', 'usergroup.none'),
41 ('read', 'usergroup.read'),
41 ('read', 'usergroup.read'),
42 ('write', 'usergroup.write'),
42 ('write', 'usergroup.write'),
43 ('admin', 'usergroup.admin'),
43 ('admin', 'usergroup.admin'),
44
44
45 ('none', 'usergroup.none'),
45 ('none', 'usergroup.none'),
46 ('read', 'usergroup.read'),
46 ('read', 'usergroup.read'),
47 ('write', 'usergroup.write'),
47 ('write', 'usergroup.write'),
48 ('admin', 'usergroup.admin'),
48 ('admin', 'usergroup.admin'),
49 ])
49 ])
50 def test_api_grant_user_group_permission_to_user_group(
50 def test_api_grant_user_group_permission_to_user_group(
51 self, name, perm, user_util):
51 self, name, perm, user_util):
52 group = user_util.create_user_group()
52 group = user_util.create_user_group()
53 target_group = user_util.create_user_group()
53 target_group = user_util.create_user_group()
54
54
55 id_, params = build_data(
55 id_, params = build_data(
56 self.apikey,
56 self.apikey,
57 'grant_user_group_permission_to_user_group',
57 'grant_user_group_permission_to_user_group',
58 usergroupid=target_group.users_group_name,
58 usergroupid=target_group.users_group_name,
59 sourceusergroupid=group.users_group_name,
59 sourceusergroupid=group.users_group_name,
60 perm=perm)
60 perm=perm)
61 response = api_call(self.app, params)
61 response = api_call(self.app, params)
62
62
63 expected = {
63 expected = {
64 'msg': (
64 'msg': (
65 'Granted perm: `%s` for user group: `%s`'
65 'Granted perm: `%s` for user group: `%s`'
66 ' in user group: `%s`' % (
66 ' in user group: `%s`' % (
67 perm, group.users_group_name,
67 perm, group.users_group_name,
68 target_group.users_group_name
68 target_group.users_group_name
69 )
69 )
70 ),
70 ),
71 'success': True
71 'success': True
72 }
72 }
73 try:
73 try:
74 assert_ok(id_, expected, given=response.body)
74 assert_ok(id_, expected, given=response.body)
75 finally:
75 finally:
76 UserGroupModel().revoke_user_group_permission(
76 UserGroupModel().revoke_user_group_permission(
77 target_group.users_group_id, group.users_group_id)
77 target_group.users_group_id, group.users_group_id)
78
78
79 def test_api_grant_user_group_permission_to_user_group_same_failure(
79 def test_api_grant_user_group_permission_to_user_group_same_failure(
80 self, user_util):
80 self, user_util):
81 group = user_util.create_user_group()
81 group = user_util.create_user_group()
82
82
83 id_, params = build_data(
83 id_, params = build_data(
84 self.apikey,
84 self.apikey,
85 'grant_user_group_permission_to_user_group',
85 'grant_user_group_permission_to_user_group',
86 usergroupid=group.users_group_name,
86 usergroupid=group.users_group_name,
87 sourceusergroupid=group.users_group_name,
87 sourceusergroupid=group.users_group_name,
88 perm='usergroup.none')
88 perm='usergroup.none')
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90
90
91 expected = (
91 expected = (
92 'failed to edit permission for user group: `%s`'
92 'failed to edit permission for user group: `%s`'
93 ' in user group: `%s`' % (
93 ' in user group: `%s`' % (
94 group.users_group_name, group.users_group_name)
94 group.users_group_name, group.users_group_name)
95 )
95 )
96 assert_error(id_, expected, given=response.body)
96 assert_error(id_, expected, given=response.body)
@@ -1,86 +1,86 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo import RepoModel
23 from rhodecode.model.repo import RepoModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestGrantUserPermission(object):
29 class TestGrantUserPermission(object):
30 @pytest.mark.parametrize("name, perm", [
30 @pytest.mark.parametrize("name, perm", [
31 ('none', 'repository.none'),
31 ('none', 'repository.none'),
32 ('read', 'repository.read'),
32 ('read', 'repository.read'),
33 ('write', 'repository.write'),
33 ('write', 'repository.write'),
34 ('admin', 'repository.admin')
34 ('admin', 'repository.admin')
35 ])
35 ])
36 def test_api_grant_user_permission(self, name, perm, backend, user_util):
36 def test_api_grant_user_permission(self, name, perm, backend, user_util):
37 user = user_util.create_user()
37 user = user_util.create_user()
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey,
39 self.apikey,
40 'grant_user_permission',
40 'grant_user_permission',
41 repoid=backend.repo_name,
41 repoid=backend.repo_name,
42 userid=user.username,
42 userid=user.username,
43 perm=perm)
43 perm=perm)
44 response = api_call(self.app, params)
44 response = api_call(self.app, params)
45
45
46 ret = {
46 ret = {
47 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
47 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % (
48 perm, user.username, backend.repo_name
48 perm, user.username, backend.repo_name
49 ),
49 ),
50 'success': True
50 'success': True
51 }
51 }
52 expected = ret
52 expected = ret
53 assert_ok(id_, expected, given=response.body)
53 assert_ok(id_, expected, given=response.body)
54
54
55 def test_api_grant_user_permission_wrong_permission(
55 def test_api_grant_user_permission_wrong_permission(
56 self, backend, user_util):
56 self, backend, user_util):
57 user = user_util.create_user()
57 user = user_util.create_user()
58 perm = 'haha.no.permission'
58 perm = 'haha.no.permission'
59 id_, params = build_data(
59 id_, params = build_data(
60 self.apikey,
60 self.apikey,
61 'grant_user_permission',
61 'grant_user_permission',
62 repoid=backend.repo_name,
62 repoid=backend.repo_name,
63 userid=user.username,
63 userid=user.username,
64 perm=perm)
64 perm=perm)
65 response = api_call(self.app, params)
65 response = api_call(self.app, params)
66
66
67 expected = 'permission `%s` does not exist.' % (perm,)
67 expected = 'permission `%s` does not exist.' % (perm,)
68 assert_error(id_, expected, given=response.body)
68 assert_error(id_, expected, given=response.body)
69
69
70 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
70 @mock.patch.object(RepoModel, 'grant_user_permission', crash)
71 def test_api_grant_user_permission_exception_when_adding(
71 def test_api_grant_user_permission_exception_when_adding(
72 self, backend, user_util):
72 self, backend, user_util):
73 user = user_util.create_user()
73 user = user_util.create_user()
74 perm = 'repository.read'
74 perm = 'repository.read'
75 id_, params = build_data(
75 id_, params = build_data(
76 self.apikey,
76 self.apikey,
77 'grant_user_permission',
77 'grant_user_permission',
78 repoid=backend.repo_name,
78 repoid=backend.repo_name,
79 userid=user.username,
79 userid=user.username,
80 perm=perm)
80 perm=perm)
81 response = api_call(self.app, params)
81 response = api_call(self.app, params)
82
82
83 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
83 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
84 user.username, backend.repo_name
84 user.username, backend.repo_name
85 )
85 )
86 assert_error(id_, expected, given=response.body)
86 assert_error(id_, expected, given=response.body)
@@ -1,156 +1,156 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGrantUserPermissionFromRepoGroup(object):
30 class TestGrantUserPermissionFromRepoGroup(object):
31 @pytest.mark.parametrize("name, perm, apply_to_children", [
31 @pytest.mark.parametrize("name, perm, apply_to_children", [
32 ('none', 'group.none', 'none'),
32 ('none', 'group.none', 'none'),
33 ('read', 'group.read', 'none'),
33 ('read', 'group.read', 'none'),
34 ('write', 'group.write', 'none'),
34 ('write', 'group.write', 'none'),
35 ('admin', 'group.admin', 'none'),
35 ('admin', 'group.admin', 'none'),
36
36
37 ('none', 'group.none', 'all'),
37 ('none', 'group.none', 'all'),
38 ('read', 'group.read', 'all'),
38 ('read', 'group.read', 'all'),
39 ('write', 'group.write', 'all'),
39 ('write', 'group.write', 'all'),
40 ('admin', 'group.admin', 'all'),
40 ('admin', 'group.admin', 'all'),
41
41
42 ('none', 'group.none', 'repos'),
42 ('none', 'group.none', 'repos'),
43 ('read', 'group.read', 'repos'),
43 ('read', 'group.read', 'repos'),
44 ('write', 'group.write', 'repos'),
44 ('write', 'group.write', 'repos'),
45 ('admin', 'group.admin', 'repos'),
45 ('admin', 'group.admin', 'repos'),
46
46
47 ('none', 'group.none', 'groups'),
47 ('none', 'group.none', 'groups'),
48 ('read', 'group.read', 'groups'),
48 ('read', 'group.read', 'groups'),
49 ('write', 'group.write', 'groups'),
49 ('write', 'group.write', 'groups'),
50 ('admin', 'group.admin', 'groups'),
50 ('admin', 'group.admin', 'groups'),
51 ])
51 ])
52 def test_api_grant_user_permission_to_repo_group(
52 def test_api_grant_user_permission_to_repo_group(
53 self, name, perm, apply_to_children, user_util):
53 self, name, perm, apply_to_children, user_util):
54 user = user_util.create_user()
54 user = user_util.create_user()
55 repo_group = user_util.create_repo_group()
55 repo_group = user_util.create_repo_group()
56 id_, params = build_data(
56 id_, params = build_data(
57 self.apikey, 'grant_user_permission_to_repo_group',
57 self.apikey, 'grant_user_permission_to_repo_group',
58 repogroupid=repo_group.name, userid=user.username,
58 repogroupid=repo_group.name, userid=user.username,
59 perm=perm, apply_to_children=apply_to_children)
59 perm=perm, apply_to_children=apply_to_children)
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61
61
62 ret = {
62 ret = {
63 'msg': (
63 'msg': (
64 'Granted perm: `%s` (recursive:%s) for user: `%s`'
64 'Granted perm: `%s` (recursive:%s) for user: `%s`'
65 ' in repo group: `%s`' % (
65 ' in repo group: `%s`' % (
66 perm, apply_to_children, user.username, repo_group.name
66 perm, apply_to_children, user.username, repo_group.name
67 )
67 )
68 ),
68 ),
69 'success': True
69 'success': True
70 }
70 }
71 expected = ret
71 expected = ret
72 assert_ok(id_, expected, given=response.body)
72 assert_ok(id_, expected, given=response.body)
73
73
74 @pytest.mark.parametrize(
74 @pytest.mark.parametrize(
75 "name, perm, apply_to_children, grant_admin, access_ok", [
75 "name, perm, apply_to_children, grant_admin, access_ok", [
76 ('none_fails', 'group.none', 'none', False, False),
76 ('none_fails', 'group.none', 'none', False, False),
77 ('read_fails', 'group.read', 'none', False, False),
77 ('read_fails', 'group.read', 'none', False, False),
78 ('write_fails', 'group.write', 'none', False, False),
78 ('write_fails', 'group.write', 'none', False, False),
79 ('admin_fails', 'group.admin', 'none', False, False),
79 ('admin_fails', 'group.admin', 'none', False, False),
80
80
81 # with granted perms
81 # with granted perms
82 ('none_ok', 'group.none', 'none', True, True),
82 ('none_ok', 'group.none', 'none', True, True),
83 ('read_ok', 'group.read', 'none', True, True),
83 ('read_ok', 'group.read', 'none', True, True),
84 ('write_ok', 'group.write', 'none', True, True),
84 ('write_ok', 'group.write', 'none', True, True),
85 ('admin_ok', 'group.admin', 'none', True, True),
85 ('admin_ok', 'group.admin', 'none', True, True),
86 ]
86 ]
87 )
87 )
88 def test_api_grant_user_permission_to_repo_group_by_regular_user(
88 def test_api_grant_user_permission_to_repo_group_by_regular_user(
89 self, name, perm, apply_to_children, grant_admin, access_ok,
89 self, name, perm, apply_to_children, grant_admin, access_ok,
90 user_util):
90 user_util):
91 user = user_util.create_user()
91 user = user_util.create_user()
92 repo_group = user_util.create_repo_group()
92 repo_group = user_util.create_repo_group()
93
93
94 if grant_admin:
94 if grant_admin:
95 test_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
95 test_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
96 user_util.grant_user_permission_to_repo_group(
96 user_util.grant_user_permission_to_repo_group(
97 repo_group, test_user, 'group.admin')
97 repo_group, test_user, 'group.admin')
98
98
99 id_, params = build_data(
99 id_, params = build_data(
100 self.apikey_regular, 'grant_user_permission_to_repo_group',
100 self.apikey_regular, 'grant_user_permission_to_repo_group',
101 repogroupid=repo_group.name, userid=user.username,
101 repogroupid=repo_group.name, userid=user.username,
102 perm=perm, apply_to_children=apply_to_children)
102 perm=perm, apply_to_children=apply_to_children)
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104 if access_ok:
104 if access_ok:
105 ret = {
105 ret = {
106 'msg': (
106 'msg': (
107 'Granted perm: `%s` (recursive:%s) for user: `%s`'
107 'Granted perm: `%s` (recursive:%s) for user: `%s`'
108 ' in repo group: `%s`' % (
108 ' in repo group: `%s`' % (
109 perm, apply_to_children, user.username, repo_group.name
109 perm, apply_to_children, user.username, repo_group.name
110 )
110 )
111 ),
111 ),
112 'success': True
112 'success': True
113 }
113 }
114 expected = ret
114 expected = ret
115 assert_ok(id_, expected, given=response.body)
115 assert_ok(id_, expected, given=response.body)
116 else:
116 else:
117 expected = 'repository group `%s` does not exist' % (
117 expected = 'repository group `%s` does not exist' % (
118 repo_group.name, )
118 repo_group.name, )
119 assert_error(id_, expected, given=response.body)
119 assert_error(id_, expected, given=response.body)
120
120
121 def test_api_grant_user_permission_to_repo_group_wrong_permission(
121 def test_api_grant_user_permission_to_repo_group_wrong_permission(
122 self, user_util):
122 self, user_util):
123 user = user_util.create_user()
123 user = user_util.create_user()
124 repo_group = user_util.create_repo_group()
124 repo_group = user_util.create_repo_group()
125 perm = 'haha.no.permission'
125 perm = 'haha.no.permission'
126 id_, params = build_data(
126 id_, params = build_data(
127 self.apikey,
127 self.apikey,
128 'grant_user_permission_to_repo_group',
128 'grant_user_permission_to_repo_group',
129 repogroupid=repo_group.name,
129 repogroupid=repo_group.name,
130 userid=user.username,
130 userid=user.username,
131 perm=perm)
131 perm=perm)
132 response = api_call(self.app, params)
132 response = api_call(self.app, params)
133
133
134 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,)
134 expected = 'permission `%s` does not exist. Permission should start with prefix: `group.`' % (perm,)
135 assert_error(id_, expected, given=response.body)
135 assert_error(id_, expected, given=response.body)
136
136
137 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
137 @mock.patch.object(RepoGroupModel, 'grant_user_permission', crash)
138 def test_api_grant_user_permission_to_repo_group_exception_when_adding(
138 def test_api_grant_user_permission_to_repo_group_exception_when_adding(
139 self, user_util):
139 self, user_util):
140 user = user_util.create_user()
140 user = user_util.create_user()
141 repo_group = user_util.create_repo_group()
141 repo_group = user_util.create_repo_group()
142 perm = 'group.read'
142 perm = 'group.read'
143 id_, params = build_data(
143 id_, params = build_data(
144 self.apikey,
144 self.apikey,
145 'grant_user_permission_to_repo_group',
145 'grant_user_permission_to_repo_group',
146 repogroupid=repo_group.name,
146 repogroupid=repo_group.name,
147 userid=user.username,
147 userid=user.username,
148 perm=perm)
148 perm=perm)
149 response = api_call(self.app, params)
149 response = api_call(self.app, params)
150
150
151 expected = (
151 expected = (
152 'failed to edit permission for user: `%s` in repo group: `%s`' % (
152 'failed to edit permission for user: `%s` in repo group: `%s`' % (
153 user.username, repo_group.name
153 user.username, repo_group.name
154 )
154 )
155 )
155 )
156 assert_error(id_, expected, given=response.body)
156 assert_error(id_, expected, given=response.body)
@@ -1,155 +1,155 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestGrantUserPermissionFromUserGroup(object):
30 class TestGrantUserPermissionFromUserGroup(object):
31 @pytest.mark.parametrize("name, perm", [
31 @pytest.mark.parametrize("name, perm", [
32 ('none', 'usergroup.none'),
32 ('none', 'usergroup.none'),
33 ('read', 'usergroup.read'),
33 ('read', 'usergroup.read'),
34 ('write', 'usergroup.write'),
34 ('write', 'usergroup.write'),
35 ('admin', 'usergroup.admin'),
35 ('admin', 'usergroup.admin'),
36
36
37 ('none', 'usergroup.none'),
37 ('none', 'usergroup.none'),
38 ('read', 'usergroup.read'),
38 ('read', 'usergroup.read'),
39 ('write', 'usergroup.write'),
39 ('write', 'usergroup.write'),
40 ('admin', 'usergroup.admin'),
40 ('admin', 'usergroup.admin'),
41
41
42 ('none', 'usergroup.none'),
42 ('none', 'usergroup.none'),
43 ('read', 'usergroup.read'),
43 ('read', 'usergroup.read'),
44 ('write', 'usergroup.write'),
44 ('write', 'usergroup.write'),
45 ('admin', 'usergroup.admin'),
45 ('admin', 'usergroup.admin'),
46
46
47 ('none', 'usergroup.none'),
47 ('none', 'usergroup.none'),
48 ('read', 'usergroup.read'),
48 ('read', 'usergroup.read'),
49 ('write', 'usergroup.write'),
49 ('write', 'usergroup.write'),
50 ('admin', 'usergroup.admin'),
50 ('admin', 'usergroup.admin'),
51 ])
51 ])
52 def test_api_grant_user_permission_to_user_group(
52 def test_api_grant_user_permission_to_user_group(
53 self, name, perm, user_util):
53 self, name, perm, user_util):
54 user = user_util.create_user()
54 user = user_util.create_user()
55 group = user_util.create_user_group()
55 group = user_util.create_user_group()
56 id_, params = build_data(
56 id_, params = build_data(
57 self.apikey,
57 self.apikey,
58 'grant_user_permission_to_user_group',
58 'grant_user_permission_to_user_group',
59 usergroupid=group.users_group_name,
59 usergroupid=group.users_group_name,
60 userid=user.username,
60 userid=user.username,
61 perm=perm)
61 perm=perm)
62 response = api_call(self.app, params)
62 response = api_call(self.app, params)
63
63
64 ret = {
64 ret = {
65 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
65 'msg': 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
66 perm, user.username, group.users_group_name
66 perm, user.username, group.users_group_name
67 ),
67 ),
68 'success': True
68 'success': True
69 }
69 }
70 expected = ret
70 expected = ret
71 assert_ok(id_, expected, given=response.body)
71 assert_ok(id_, expected, given=response.body)
72
72
73 @pytest.mark.parametrize("name, perm, grant_admin, access_ok", [
73 @pytest.mark.parametrize("name, perm, grant_admin, access_ok", [
74 ('none_fails', 'usergroup.none', False, False),
74 ('none_fails', 'usergroup.none', False, False),
75 ('read_fails', 'usergroup.read', False, False),
75 ('read_fails', 'usergroup.read', False, False),
76 ('write_fails', 'usergroup.write', False, False),
76 ('write_fails', 'usergroup.write', False, False),
77 ('admin_fails', 'usergroup.admin', False, False),
77 ('admin_fails', 'usergroup.admin', False, False),
78
78
79 # with granted perms
79 # with granted perms
80 ('none_ok', 'usergroup.none', True, True),
80 ('none_ok', 'usergroup.none', True, True),
81 ('read_ok', 'usergroup.read', True, True),
81 ('read_ok', 'usergroup.read', True, True),
82 ('write_ok', 'usergroup.write', True, True),
82 ('write_ok', 'usergroup.write', True, True),
83 ('admin_ok', 'usergroup.admin', True, True),
83 ('admin_ok', 'usergroup.admin', True, True),
84 ])
84 ])
85 def test_api_grant_user_permission_to_user_group_by_regular_user(
85 def test_api_grant_user_permission_to_user_group_by_regular_user(
86 self, name, perm, grant_admin, access_ok, user_util):
86 self, name, perm, grant_admin, access_ok, user_util):
87 api_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
87 api_user = UserModel().get_by_username(self.TEST_USER_LOGIN)
88 user = user_util.create_user()
88 user = user_util.create_user()
89 group = user_util.create_user_group()
89 group = user_util.create_user_group()
90 # grant the user ability to at least read the group
90 # grant the user ability to at least read the group
91 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
91 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
92 user_util.grant_user_permission_to_user_group(
92 user_util.grant_user_permission_to_user_group(
93 group, api_user, permission)
93 group, api_user, permission)
94
94
95 id_, params = build_data(
95 id_, params = build_data(
96 self.apikey_regular,
96 self.apikey_regular,
97 'grant_user_permission_to_user_group',
97 'grant_user_permission_to_user_group',
98 usergroupid=group.users_group_name,
98 usergroupid=group.users_group_name,
99 userid=user.username,
99 userid=user.username,
100 perm=perm)
100 perm=perm)
101 response = api_call(self.app, params)
101 response = api_call(self.app, params)
102
102
103 if access_ok:
103 if access_ok:
104 ret = {
104 ret = {
105 'msg': (
105 'msg': (
106 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
106 'Granted perm: `%s` for user: `%s` in user group: `%s`' % (
107 perm, user.username, group.users_group_name
107 perm, user.username, group.users_group_name
108 )
108 )
109 ),
109 ),
110 'success': True
110 'success': True
111 }
111 }
112 expected = ret
112 expected = ret
113 assert_ok(id_, expected, given=response.body)
113 assert_ok(id_, expected, given=response.body)
114 else:
114 else:
115 expected = 'user group `%s` does not exist' % (
115 expected = 'user group `%s` does not exist' % (
116 group.users_group_name)
116 group.users_group_name)
117 assert_error(id_, expected, given=response.body)
117 assert_error(id_, expected, given=response.body)
118
118
119 def test_api_grant_user_permission_to_user_group_wrong_permission(
119 def test_api_grant_user_permission_to_user_group_wrong_permission(
120 self, user_util):
120 self, user_util):
121 user = user_util.create_user()
121 user = user_util.create_user()
122 group = user_util.create_user_group()
122 group = user_util.create_user_group()
123 perm = 'haha.no.permission'
123 perm = 'haha.no.permission'
124 id_, params = build_data(
124 id_, params = build_data(
125 self.apikey,
125 self.apikey,
126 'grant_user_permission_to_user_group',
126 'grant_user_permission_to_user_group',
127 usergroupid=group.users_group_name,
127 usergroupid=group.users_group_name,
128 userid=user.username,
128 userid=user.username,
129 perm=perm)
129 perm=perm)
130 response = api_call(self.app, params)
130 response = api_call(self.app, params)
131
131
132 expected = 'permission `%s` does not exist. Permission should start with prefix: `usergroup.`' % perm
132 expected = 'permission `%s` does not exist. Permission should start with prefix: `usergroup.`' % perm
133 assert_error(id_, expected, given=response.body)
133 assert_error(id_, expected, given=response.body)
134
134
135 def test_api_grant_user_permission_to_user_group_exception_when_adding(
135 def test_api_grant_user_permission_to_user_group_exception_when_adding(
136 self, user_util):
136 self, user_util):
137 user = user_util.create_user()
137 user = user_util.create_user()
138 group = user_util.create_user_group()
138 group = user_util.create_user_group()
139
139
140 perm = 'usergroup.read'
140 perm = 'usergroup.read'
141 id_, params = build_data(
141 id_, params = build_data(
142 self.apikey,
142 self.apikey,
143 'grant_user_permission_to_user_group',
143 'grant_user_permission_to_user_group',
144 usergroupid=group.users_group_name,
144 usergroupid=group.users_group_name,
145 userid=user.username,
145 userid=user.username,
146 perm=perm)
146 perm=perm)
147 with mock.patch.object(UserGroupModel, 'grant_user_permission', crash):
147 with mock.patch.object(UserGroupModel, 'grant_user_permission', crash):
148 response = api_call(self.app, params)
148 response = api_call(self.app, params)
149
149
150 expected = (
150 expected = (
151 'failed to edit permission for user: `%s` in user group: `%s`' % (
151 'failed to edit permission for user: `%s` in user group: `%s`' % (
152 user.username, group.users_group_name
152 user.username, group.users_group_name
153 )
153 )
154 )
154 )
155 assert_error(id_, expected, given=response.body)
155 assert_error(id_, expected, given=response.body)
@@ -1,67 +1,67 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.scm import ScmModel
23 from rhodecode.model.scm import ScmModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error, crash)
25 build_data, api_call, assert_ok, assert_error, crash)
26 from rhodecode.model.repo import RepoModel
26 from rhodecode.model.repo import RepoModel
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestInvalidateCache(object):
30 class TestInvalidateCache(object):
31
31
32 def _set_cache(self, repo_name):
32 def _set_cache(self, repo_name):
33 repo = RepoModel().get_by_repo_name(repo_name)
33 repo = RepoModel().get_by_repo_name(repo_name)
34 repo.scm_instance(cache=True)
34 repo.scm_instance(cache=True)
35
35
36 def test_api_invalidate_cache(self, backend):
36 def test_api_invalidate_cache(self, backend):
37 self._set_cache(backend.repo_name)
37 self._set_cache(backend.repo_name)
38
38
39 id_, params = build_data(
39 id_, params = build_data(
40 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
40 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = {
43 expected = {
44 'msg': "Cache for repository `%s` was invalidated" % (
44 'msg': "Cache for repository `%s` was invalidated" % (
45 backend.repo_name,),
45 backend.repo_name,),
46 'repository': backend.repo_name,
46 'repository': backend.repo_name,
47 }
47 }
48 assert_ok(id_, expected, given=response.body)
48 assert_ok(id_, expected, given=response.body)
49
49
50 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
50 @mock.patch.object(ScmModel, 'mark_for_invalidation', crash)
51 def test_api_invalidate_cache_error(self, backend):
51 def test_api_invalidate_cache_error(self, backend):
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
53 self.apikey, 'invalidate_cache', repoid=backend.repo_name)
54 response = api_call(self.app, params)
54 response = api_call(self.app, params)
55
55
56 expected = 'Error occurred during cache invalidation action'
56 expected = 'Error occurred during cache invalidation action'
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
58
58
59 def test_api_invalidate_cache_regular_user_no_permission(self, backend):
59 def test_api_invalidate_cache_regular_user_no_permission(self, backend):
60 self._set_cache(backend.repo_name)
60 self._set_cache(backend.repo_name)
61
61
62 id_, params = build_data(
62 id_, params = build_data(
63 self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name)
63 self.apikey_regular, 'invalidate_cache', repoid=backend.repo_name)
64 response = api_call(self.app, params)
64 response = api_call(self.app, params)
65
65
66 expected = "repository `%s` does not exist" % (backend.repo_name,)
66 expected = "repository `%s` does not exist" % (backend.repo_name,)
67 assert_error(id_, expected, given=response.body)
67 assert_error(id_, expected, given=response.body)
@@ -1,258 +1,258 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.db import UserLog, PullRequest
22 from rhodecode.model.db import UserLog, PullRequest
23 from rhodecode.model.meta import Session
23 from rhodecode.model.meta import Session
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok)
26 build_data, api_call, assert_error, assert_ok)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestMergePullRequest(object):
30 class TestMergePullRequest(object):
31
31
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
33 def test_api_merge_pull_request_merge_failed(self, pr_util, no_notifications):
34 pull_request = pr_util.create_pull_request(mergeable=True)
34 pull_request = pr_util.create_pull_request(mergeable=True)
35 pull_request_id = pull_request.pull_request_id
35 pull_request_id = pull_request.pull_request_id
36 pull_request_repo = pull_request.target_repo.repo_name
36 pull_request_repo = pull_request.target_repo.repo_name
37
37
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey, 'merge_pull_request',
39 self.apikey, 'merge_pull_request',
40 repoid=pull_request_repo,
40 repoid=pull_request_repo,
41 pullrequestid=pull_request_id)
41 pullrequestid=pull_request_id)
42
42
43 response = api_call(self.app, params)
43 response = api_call(self.app, params)
44
44
45 # The above api call detaches the pull request DB object from the
45 # The above api call detaches the pull request DB object from the
46 # session because of an unconditional transaction rollback in our
46 # session because of an unconditional transaction rollback in our
47 # middleware. Therefore we need to add it back here if we want to use it.
47 # middleware. Therefore we need to add it back here if we want to use it.
48 Session().add(pull_request)
48 Session().add(pull_request)
49
49
50 expected = 'merge not possible for following reasons: ' \
50 expected = 'merge not possible for following reasons: ' \
51 'Pull request reviewer approval is pending.'
51 'Pull request reviewer approval is pending.'
52 assert_error(id_, expected, given=response.body)
52 assert_error(id_, expected, given=response.body)
53
53
54 @pytest.mark.backends("git", "hg")
54 @pytest.mark.backends("git", "hg")
55 def test_api_merge_pull_request_merge_failed_disallowed_state(
55 def test_api_merge_pull_request_merge_failed_disallowed_state(
56 self, pr_util, no_notifications):
56 self, pr_util, no_notifications):
57 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
57 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
58 pull_request_id = pull_request.pull_request_id
58 pull_request_id = pull_request.pull_request_id
59 pull_request_repo = pull_request.target_repo.repo_name
59 pull_request_repo = pull_request.target_repo.repo_name
60
60
61 pr = PullRequest.get(pull_request_id)
61 pr = PullRequest.get(pull_request_id)
62 pr.pull_request_state = pull_request.STATE_UPDATING
62 pr.pull_request_state = pull_request.STATE_UPDATING
63 Session().add(pr)
63 Session().add(pr)
64 Session().commit()
64 Session().commit()
65
65
66 id_, params = build_data(
66 id_, params = build_data(
67 self.apikey, 'merge_pull_request',
67 self.apikey, 'merge_pull_request',
68 repoid=pull_request_repo,
68 repoid=pull_request_repo,
69 pullrequestid=pull_request_id)
69 pullrequestid=pull_request_id)
70
70
71 response = api_call(self.app, params)
71 response = api_call(self.app, params)
72 expected = 'Operation forbidden because pull request is in state {}, '\
72 expected = 'Operation forbidden because pull request is in state {}, '\
73 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING,
73 'only state {} is allowed.'.format(PullRequest.STATE_UPDATING,
74 PullRequest.STATE_CREATED)
74 PullRequest.STATE_CREATED)
75 assert_error(id_, expected, given=response.body)
75 assert_error(id_, expected, given=response.body)
76
76
77 @pytest.mark.backends("git", "hg")
77 @pytest.mark.backends("git", "hg")
78 def test_api_merge_pull_request(self, pr_util, no_notifications):
78 def test_api_merge_pull_request(self, pr_util, no_notifications):
79 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
79 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
80 author = pull_request.user_id
80 author = pull_request.user_id
81 repo = pull_request.target_repo.repo_id
81 repo = pull_request.target_repo.repo_id
82 pull_request_id = pull_request.pull_request_id
82 pull_request_id = pull_request.pull_request_id
83 pull_request_repo = pull_request.target_repo.repo_name
83 pull_request_repo = pull_request.target_repo.repo_name
84
84
85 id_, params = build_data(
85 id_, params = build_data(
86 self.apikey, 'comment_pull_request',
86 self.apikey, 'comment_pull_request',
87 repoid=pull_request_repo,
87 repoid=pull_request_repo,
88 pullrequestid=pull_request_id,
88 pullrequestid=pull_request_id,
89 status='approved')
89 status='approved')
90
90
91 response = api_call(self.app, params)
91 response = api_call(self.app, params)
92 expected = {
92 expected = {
93 'comment_id': response.json.get('result', {}).get('comment_id'),
93 'comment_id': response.json.get('result', {}).get('comment_id'),
94 'pull_request_id': pull_request_id,
94 'pull_request_id': pull_request_id,
95 'status': {'given': 'approved', 'was_changed': True}
95 'status': {'given': 'approved', 'was_changed': True}
96 }
96 }
97 assert_ok(id_, expected, given=response.body)
97 assert_ok(id_, expected, given=response.body)
98
98
99 id_, params = build_data(
99 id_, params = build_data(
100 self.apikey, 'merge_pull_request',
100 self.apikey, 'merge_pull_request',
101 repoid=pull_request_repo,
101 repoid=pull_request_repo,
102 pullrequestid=pull_request_id)
102 pullrequestid=pull_request_id)
103
103
104 response = api_call(self.app, params)
104 response = api_call(self.app, params)
105
105
106 pull_request = PullRequest.get(pull_request_id)
106 pull_request = PullRequest.get(pull_request_id)
107
107
108 expected = {
108 expected = {
109 'executed': True,
109 'executed': True,
110 'failure_reason': 0,
110 'failure_reason': 0,
111 'merge_status_message': 'This pull request can be automatically merged.',
111 'merge_status_message': 'This pull request can be automatically merged.',
112 'possible': True,
112 'possible': True,
113 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
113 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
114 'merge_ref': pull_request.shadow_merge_ref.asdict()
114 'merge_ref': pull_request.shadow_merge_ref.asdict()
115 }
115 }
116
116
117 assert_ok(id_, expected, response.body)
117 assert_ok(id_, expected, response.body)
118
118
119 journal = UserLog.query()\
119 journal = UserLog.query()\
120 .filter(UserLog.user_id == author)\
120 .filter(UserLog.user_id == author)\
121 .filter(UserLog.repository_id == repo) \
121 .filter(UserLog.repository_id == repo) \
122 .order_by(UserLog.user_log_id.asc()) \
122 .order_by(UserLog.user_log_id.asc()) \
123 .all()
123 .all()
124 assert journal[-2].action == 'repo.pull_request.merge'
124 assert journal[-2].action == 'repo.pull_request.merge'
125 assert journal[-1].action == 'repo.pull_request.close'
125 assert journal[-1].action == 'repo.pull_request.close'
126
126
127 id_, params = build_data(
127 id_, params = build_data(
128 self.apikey, 'merge_pull_request',
128 self.apikey, 'merge_pull_request',
129 repoid=pull_request_repo, pullrequestid=pull_request_id)
129 repoid=pull_request_repo, pullrequestid=pull_request_id)
130 response = api_call(self.app, params)
130 response = api_call(self.app, params)
131
131
132 expected = 'merge not possible for following reasons: This pull request is closed.'
132 expected = 'merge not possible for following reasons: This pull request is closed.'
133 assert_error(id_, expected, given=response.body)
133 assert_error(id_, expected, given=response.body)
134
134
135 @pytest.mark.backends("git", "hg")
135 @pytest.mark.backends("git", "hg")
136 def test_api_merge_pull_request_as_another_user_no_perms_to_merge(
136 def test_api_merge_pull_request_as_another_user_no_perms_to_merge(
137 self, pr_util, no_notifications, user_util):
137 self, pr_util, no_notifications, user_util):
138 merge_user = user_util.create_user()
138 merge_user = user_util.create_user()
139 merge_user_id = merge_user.user_id
139 merge_user_id = merge_user.user_id
140 merge_user_username = merge_user.username
140 merge_user_username = merge_user.username
141
141
142 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
142 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
143
143
144 pull_request_id = pull_request.pull_request_id
144 pull_request_id = pull_request.pull_request_id
145 pull_request_repo = pull_request.target_repo.repo_name
145 pull_request_repo = pull_request.target_repo.repo_name
146
146
147 id_, params = build_data(
147 id_, params = build_data(
148 self.apikey, 'comment_pull_request',
148 self.apikey, 'comment_pull_request',
149 repoid=pull_request_repo,
149 repoid=pull_request_repo,
150 pullrequestid=pull_request_id,
150 pullrequestid=pull_request_id,
151 status='approved')
151 status='approved')
152
152
153 response = api_call(self.app, params)
153 response = api_call(self.app, params)
154 expected = {
154 expected = {
155 'comment_id': response.json.get('result', {}).get('comment_id'),
155 'comment_id': response.json.get('result', {}).get('comment_id'),
156 'pull_request_id': pull_request_id,
156 'pull_request_id': pull_request_id,
157 'status': {'given': 'approved', 'was_changed': True}
157 'status': {'given': 'approved', 'was_changed': True}
158 }
158 }
159 assert_ok(id_, expected, given=response.body)
159 assert_ok(id_, expected, given=response.body)
160 id_, params = build_data(
160 id_, params = build_data(
161 self.apikey, 'merge_pull_request',
161 self.apikey, 'merge_pull_request',
162 repoid=pull_request_repo,
162 repoid=pull_request_repo,
163 pullrequestid=pull_request_id,
163 pullrequestid=pull_request_id,
164 userid=merge_user_id
164 userid=merge_user_id
165 )
165 )
166
166
167 response = api_call(self.app, params)
167 response = api_call(self.app, params)
168 expected = 'merge not possible for following reasons: User `{}` ' \
168 expected = 'merge not possible for following reasons: User `{}` ' \
169 'not allowed to perform merge.'.format(merge_user_username)
169 'not allowed to perform merge.'.format(merge_user_username)
170 assert_error(id_, expected, response.body)
170 assert_error(id_, expected, response.body)
171
171
172 @pytest.mark.backends("git", "hg")
172 @pytest.mark.backends("git", "hg")
173 def test_api_merge_pull_request_as_another_user(self, pr_util, no_notifications, user_util):
173 def test_api_merge_pull_request_as_another_user(self, pr_util, no_notifications, user_util):
174 merge_user = user_util.create_user()
174 merge_user = user_util.create_user()
175 merge_user_id = merge_user.user_id
175 merge_user_id = merge_user.user_id
176 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
176 pull_request = pr_util.create_pull_request(mergeable=True, approved=True)
177 user_util.grant_user_permission_to_repo(
177 user_util.grant_user_permission_to_repo(
178 pull_request.target_repo, merge_user, 'repository.write')
178 pull_request.target_repo, merge_user, 'repository.write')
179 author = pull_request.user_id
179 author = pull_request.user_id
180 repo = pull_request.target_repo.repo_id
180 repo = pull_request.target_repo.repo_id
181 pull_request_id = pull_request.pull_request_id
181 pull_request_id = pull_request.pull_request_id
182 pull_request_repo = pull_request.target_repo.repo_name
182 pull_request_repo = pull_request.target_repo.repo_name
183
183
184 id_, params = build_data(
184 id_, params = build_data(
185 self.apikey, 'comment_pull_request',
185 self.apikey, 'comment_pull_request',
186 repoid=pull_request_repo,
186 repoid=pull_request_repo,
187 pullrequestid=pull_request_id,
187 pullrequestid=pull_request_id,
188 status='approved')
188 status='approved')
189
189
190 response = api_call(self.app, params)
190 response = api_call(self.app, params)
191 expected = {
191 expected = {
192 'comment_id': response.json.get('result', {}).get('comment_id'),
192 'comment_id': response.json.get('result', {}).get('comment_id'),
193 'pull_request_id': pull_request_id,
193 'pull_request_id': pull_request_id,
194 'status': {'given': 'approved', 'was_changed': True}
194 'status': {'given': 'approved', 'was_changed': True}
195 }
195 }
196 assert_ok(id_, expected, given=response.body)
196 assert_ok(id_, expected, given=response.body)
197
197
198 id_, params = build_data(
198 id_, params = build_data(
199 self.apikey, 'merge_pull_request',
199 self.apikey, 'merge_pull_request',
200 repoid=pull_request_repo,
200 repoid=pull_request_repo,
201 pullrequestid=pull_request_id,
201 pullrequestid=pull_request_id,
202 userid=merge_user_id
202 userid=merge_user_id
203 )
203 )
204
204
205 response = api_call(self.app, params)
205 response = api_call(self.app, params)
206
206
207 pull_request = PullRequest.get(pull_request_id)
207 pull_request = PullRequest.get(pull_request_id)
208
208
209 expected = {
209 expected = {
210 'executed': True,
210 'executed': True,
211 'failure_reason': 0,
211 'failure_reason': 0,
212 'merge_status_message': 'This pull request can be automatically merged.',
212 'merge_status_message': 'This pull request can be automatically merged.',
213 'possible': True,
213 'possible': True,
214 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
214 'merge_commit_id': pull_request.shadow_merge_ref.commit_id,
215 'merge_ref': pull_request.shadow_merge_ref.asdict()
215 'merge_ref': pull_request.shadow_merge_ref.asdict()
216 }
216 }
217
217
218 assert_ok(id_, expected, response.body)
218 assert_ok(id_, expected, response.body)
219
219
220 journal = UserLog.query() \
220 journal = UserLog.query() \
221 .filter(UserLog.user_id == merge_user_id) \
221 .filter(UserLog.user_id == merge_user_id) \
222 .filter(UserLog.repository_id == repo) \
222 .filter(UserLog.repository_id == repo) \
223 .order_by(UserLog.user_log_id.asc()) \
223 .order_by(UserLog.user_log_id.asc()) \
224 .all()
224 .all()
225 assert journal[-2].action == 'repo.pull_request.merge'
225 assert journal[-2].action == 'repo.pull_request.merge'
226 assert journal[-1].action == 'repo.pull_request.close'
226 assert journal[-1].action == 'repo.pull_request.close'
227
227
228 id_, params = build_data(
228 id_, params = build_data(
229 self.apikey, 'merge_pull_request',
229 self.apikey, 'merge_pull_request',
230 repoid=pull_request_repo, pullrequestid=pull_request_id, userid=merge_user_id)
230 repoid=pull_request_repo, pullrequestid=pull_request_id, userid=merge_user_id)
231 response = api_call(self.app, params)
231 response = api_call(self.app, params)
232
232
233 expected = 'merge not possible for following reasons: This pull request is closed.'
233 expected = 'merge not possible for following reasons: This pull request is closed.'
234 assert_error(id_, expected, given=response.body)
234 assert_error(id_, expected, given=response.body)
235
235
236 @pytest.mark.backends("git", "hg")
236 @pytest.mark.backends("git", "hg")
237 def test_api_merge_pull_request_repo_error(self, pr_util):
237 def test_api_merge_pull_request_repo_error(self, pr_util):
238 pull_request = pr_util.create_pull_request()
238 pull_request = pr_util.create_pull_request()
239 id_, params = build_data(
239 id_, params = build_data(
240 self.apikey, 'merge_pull_request',
240 self.apikey, 'merge_pull_request',
241 repoid=666, pullrequestid=pull_request.pull_request_id)
241 repoid=666, pullrequestid=pull_request.pull_request_id)
242 response = api_call(self.app, params)
242 response = api_call(self.app, params)
243
243
244 expected = 'repository `666` does not exist'
244 expected = 'repository `666` does not exist'
245 assert_error(id_, expected, given=response.body)
245 assert_error(id_, expected, given=response.body)
246
246
247 @pytest.mark.backends("git", "hg")
247 @pytest.mark.backends("git", "hg")
248 def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util):
248 def test_api_merge_pull_request_non_admin_with_userid_error(self, pr_util):
249 pull_request = pr_util.create_pull_request(mergeable=True)
249 pull_request = pr_util.create_pull_request(mergeable=True)
250 id_, params = build_data(
250 id_, params = build_data(
251 self.apikey_regular, 'merge_pull_request',
251 self.apikey_regular, 'merge_pull_request',
252 repoid=pull_request.target_repo.repo_name,
252 repoid=pull_request.target_repo.repo_name,
253 pullrequestid=pull_request.pull_request_id,
253 pullrequestid=pull_request.pull_request_id,
254 userid=TEST_USER_ADMIN_LOGIN)
254 userid=TEST_USER_ADMIN_LOGIN)
255 response = api_call(self.app, params)
255 response = api_call(self.app, params)
256
256
257 expected = 'userid is not the same as your user'
257 expected = 'userid is not the same as your user'
258 assert_error(id_, expected, given=response.body)
258 assert_error(id_, expected, given=response.body)
@@ -1,54 +1,54 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import os
20 import os
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.tests import TESTS_TMP_PATH
24 from rhodecode.tests import TESTS_TMP_PATH
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_ok, assert_error)
26 build_data, api_call, assert_ok, assert_error)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestPull(object):
30 class TestPull(object):
31
31
32 @pytest.mark.backends("git", "hg")
32 @pytest.mark.backends("git", "hg")
33 def test_api_pull(self, backend):
33 def test_api_pull(self, backend):
34 r = backend.create_repo()
34 r = backend.create_repo()
35 repo_name = r.repo_name
35 repo_name = r.repo_name
36 clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
36 clone_uri = os.path.join(TESTS_TMP_PATH, backend.repo_name)
37 r.clone_uri = clone_uri
37 r.clone_uri = clone_uri
38
38
39 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
39 id_, params = build_data(self.apikey, 'pull', repoid=repo_name,)
40 with mock.patch('rhodecode.model.scm.url_validator'):
40 with mock.patch('rhodecode.model.scm.url_validator'):
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42 msg = 'Pulled from url `%s` on repo `%s`' % (
42 msg = 'Pulled from url `%s` on repo `%s`' % (
43 clone_uri, repo_name)
43 clone_uri, repo_name)
44 expected = {'msg': msg,
44 expected = {'msg': msg,
45 'repository': repo_name}
45 'repository': repo_name}
46 assert_ok(id_, expected, given=response.body)
46 assert_ok(id_, expected, given=response.body)
47
47
48 def test_api_pull_error(self, backend):
48 def test_api_pull_error(self, backend):
49 id_, params = build_data(
49 id_, params = build_data(
50 self.apikey, 'pull', repoid=backend.repo_name)
50 self.apikey, 'pull', repoid=backend.repo_name)
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52
52
53 expected = 'Unable to pull changes from `None`'
53 expected = 'Unable to pull changes from `None`'
54 assert_error(id_, expected, given=response.body)
54 assert_error(id_, expected, given=response.body)
@@ -1,65 +1,65 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.db import Repository, RepositoryField
22 from rhodecode.model.db import Repository, RepositoryField
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24
24
25
25
26 @pytest.mark.usefixtures("testuser_api", "app")
26 @pytest.mark.usefixtures("testuser_api", "app")
27 class TestRemoveFieldFromRepo(object):
27 class TestRemoveFieldFromRepo(object):
28 def test_api_remove_field_from_repo(self, backend):
28 def test_api_remove_field_from_repo(self, backend):
29 repo = backend.create_repo()
29 repo = backend.create_repo()
30 repo_name = repo.repo_name
30 repo_name = repo.repo_name
31
31
32 id_, params = build_data(
32 id_, params = build_data(
33 self.apikey, 'add_field_to_repo',
33 self.apikey, 'add_field_to_repo',
34 repoid=repo_name,
34 repoid=repo_name,
35 key='extra_field',
35 key='extra_field',
36 label='extra_field_label',
36 label='extra_field_label',
37 description='extra_field_desc')
37 description='extra_field_desc')
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39 expected = {
39 expected = {
40 'msg': 'Added new repository field `extra_field`',
40 'msg': 'Added new repository field `extra_field`',
41 'success': True,
41 'success': True,
42 }
42 }
43 assert_ok(id_, expected, given=response.body)
43 assert_ok(id_, expected, given=response.body)
44
44
45 repo = Repository.get_by_repo_name(repo_name)
45 repo = Repository.get_by_repo_name(repo_name)
46 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
46 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
47 _data = repo_field.get_dict()
47 _data = repo_field.get_dict()
48 assert _data['field_desc'] == 'extra_field_desc'
48 assert _data['field_desc'] == 'extra_field_desc'
49 assert _data['field_key'] == 'extra_field'
49 assert _data['field_key'] == 'extra_field'
50 assert _data['field_label'] == 'extra_field_label'
50 assert _data['field_label'] == 'extra_field_label'
51
51
52 id_, params = build_data(
52 id_, params = build_data(
53 self.apikey, 'remove_field_from_repo',
53 self.apikey, 'remove_field_from_repo',
54 repoid=repo_name,
54 repoid=repo_name,
55 key='extra_field')
55 key='extra_field')
56 response = api_call(self.app, params)
56 response = api_call(self.app, params)
57 expected = {
57 expected = {
58 'msg': 'Deleted repository field `extra_field`',
58 'msg': 'Deleted repository field `extra_field`',
59 'success': True,
59 'success': True,
60 }
60 }
61 assert_ok(id_, expected, given=response.body)
61 assert_ok(id_, expected, given=response.body)
62 repo = Repository.get_by_repo_name(repo_name)
62 repo = Repository.get_by_repo_name(repo_name)
63 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
63 repo_field = RepositoryField.get_by_key_name('extra_field', repo)
64
64
65 assert repo_field is None
65 assert repo_field is None
@@ -1,57 +1,57 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user_group import UserGroupModel
23 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestRemoveUserFromUserGroup(object):
29 class TestRemoveUserFromUserGroup(object):
30 def test_api_remove_user_from_user_group(self, user_util):
30 def test_api_remove_user_from_user_group(self, user_util):
31 user, group = user_util.create_user_with_group()
31 user, group = user_util.create_user_with_group()
32 user_name = user.username
32 user_name = user.username
33 group_name = group.users_group_name
33 group_name = group.users_group_name
34 id_, params = build_data(
34 id_, params = build_data(
35 self.apikey, 'remove_user_from_user_group',
35 self.apikey, 'remove_user_from_user_group',
36 usergroupid=group_name,
36 usergroupid=group_name,
37 userid=user.username)
37 userid=user.username)
38 response = api_call(self.app, params)
38 response = api_call(self.app, params)
39
39
40 expected = {
40 expected = {
41 'msg': 'removed member `%s` from user group `%s`' % (
41 'msg': 'removed member `%s` from user group `%s`' % (
42 user_name, group_name
42 user_name, group_name
43 ),
43 ),
44 'success': True}
44 'success': True}
45 assert_ok(id_, expected, given=response.body)
45 assert_ok(id_, expected, given=response.body)
46
46
47 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
47 @mock.patch.object(UserGroupModel, 'remove_user_from_group', crash)
48 def test_api_remove_user_from_user_group_exception_occurred(
48 def test_api_remove_user_from_user_group_exception_occurred(
49 self, user_util):
49 self, user_util):
50 user, group = user_util.create_user_with_group()
50 user, group = user_util.create_user_with_group()
51 id_, params = build_data(
51 id_, params = build_data(
52 self.apikey, 'remove_user_from_user_group',
52 self.apikey, 'remove_user_from_user_group',
53 usergroupid=group.users_group_name, userid=user.username)
53 usergroupid=group.users_group_name, userid=user.username)
54 response = api_call(self.app, params)
54 response = api_call(self.app, params)
55 expected = 'failed to remove member from user group `%s`' % (
55 expected = 'failed to remove member from user group `%s`' % (
56 group.users_group_name)
56 group.users_group_name)
57 assert_error(id_, expected, given=response.body)
57 assert_error(id_, expected, given=response.body)
@@ -1,183 +1,183 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.db import Repository
24 from rhodecode.model.db import Repository
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.lib.ext_json import json
26 from rhodecode.lib.ext_json import json
27 from rhodecode.lib.utils2 import time_to_datetime
27 from rhodecode.lib.utils2 import time_to_datetime
28 from rhodecode.api.tests.utils import (
28 from rhodecode.api.tests.utils import (
29 build_data, api_call, assert_ok, assert_error, crash)
29 build_data, api_call, assert_ok, assert_error, crash)
30 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
30 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
31
31
32
32
33 @pytest.mark.usefixtures("testuser_api", "app")
33 @pytest.mark.usefixtures("testuser_api", "app")
34 class TestLock(object):
34 class TestLock(object):
35 def test_api_lock_repo_lock_aquire(self, backend):
35 def test_api_lock_repo_lock_aquire(self, backend):
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey, 'lock',
37 self.apikey, 'lock',
38 userid=TEST_USER_ADMIN_LOGIN,
38 userid=TEST_USER_ADMIN_LOGIN,
39 repoid=backend.repo_name,
39 repoid=backend.repo_name,
40 locked=True)
40 locked=True)
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42 expected = {
42 expected = {
43 'repo': backend.repo_name, 'locked': True,
43 'repo': backend.repo_name, 'locked': True,
44 'locked_since': response.json['result']['locked_since'],
44 'locked_since': response.json['result']['locked_since'],
45 'locked_by': TEST_USER_ADMIN_LOGIN,
45 'locked_by': TEST_USER_ADMIN_LOGIN,
46 'lock_state_changed': True,
46 'lock_state_changed': True,
47 'lock_reason': Repository.LOCK_API,
47 'lock_reason': Repository.LOCK_API,
48 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
48 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
49 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
49 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
50 }
50 }
51 assert_ok(id_, expected, given=response.body)
51 assert_ok(id_, expected, given=response.body)
52
52
53 def test_repo_lock_aquire_by_non_admin(self, backend):
53 def test_repo_lock_aquire_by_non_admin(self, backend):
54 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
54 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
55 repo_name = repo.repo_name
55 repo_name = repo.repo_name
56 id_, params = build_data(
56 id_, params = build_data(
57 self.apikey_regular, 'lock',
57 self.apikey_regular, 'lock',
58 repoid=repo_name,
58 repoid=repo_name,
59 locked=True)
59 locked=True)
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61 expected = {
61 expected = {
62 'repo': repo_name,
62 'repo': repo_name,
63 'locked': True,
63 'locked': True,
64 'locked_since': response.json['result']['locked_since'],
64 'locked_since': response.json['result']['locked_since'],
65 'locked_by': self.TEST_USER_LOGIN,
65 'locked_by': self.TEST_USER_LOGIN,
66 'lock_state_changed': True,
66 'lock_state_changed': True,
67 'lock_reason': Repository.LOCK_API,
67 'lock_reason': Repository.LOCK_API,
68 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
68 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
69 % (self.TEST_USER_LOGIN, repo_name, True))
69 % (self.TEST_USER_LOGIN, repo_name, True))
70 }
70 }
71 assert_ok(id_, expected, given=response.body)
71 assert_ok(id_, expected, given=response.body)
72
72
73 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend):
73 def test_api_lock_repo_lock_aquire_non_admin_with_userid(self, backend):
74 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
74 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
75 repo_name = repo.repo_name
75 repo_name = repo.repo_name
76 id_, params = build_data(
76 id_, params = build_data(
77 self.apikey_regular, 'lock',
77 self.apikey_regular, 'lock',
78 userid=TEST_USER_ADMIN_LOGIN,
78 userid=TEST_USER_ADMIN_LOGIN,
79 repoid=repo_name,
79 repoid=repo_name,
80 locked=True)
80 locked=True)
81 response = api_call(self.app, params)
81 response = api_call(self.app, params)
82 expected = 'userid is not the same as your user'
82 expected = 'userid is not the same as your user'
83 assert_error(id_, expected, given=response.body)
83 assert_error(id_, expected, given=response.body)
84
84
85 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend):
85 def test_api_lock_repo_lock_aquire_non_admin_not_his_repo(self, backend):
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey_regular, 'lock',
87 self.apikey_regular, 'lock',
88 repoid=backend.repo_name,
88 repoid=backend.repo_name,
89 locked=True)
89 locked=True)
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91 expected = 'repository `%s` does not exist' % (backend.repo_name, )
91 expected = 'repository `%s` does not exist' % (backend.repo_name, )
92 assert_error(id_, expected, given=response.body)
92 assert_error(id_, expected, given=response.body)
93
93
94 def test_api_lock_repo_lock_release(self, backend):
94 def test_api_lock_repo_lock_release(self, backend):
95 id_, params = build_data(
95 id_, params = build_data(
96 self.apikey, 'lock',
96 self.apikey, 'lock',
97 userid=TEST_USER_ADMIN_LOGIN,
97 userid=TEST_USER_ADMIN_LOGIN,
98 repoid=backend.repo_name,
98 repoid=backend.repo_name,
99 locked=False)
99 locked=False)
100 response = api_call(self.app, params)
100 response = api_call(self.app, params)
101 expected = {
101 expected = {
102 'repo': backend.repo_name,
102 'repo': backend.repo_name,
103 'locked': False,
103 'locked': False,
104 'locked_since': None,
104 'locked_since': None,
105 'locked_by': TEST_USER_ADMIN_LOGIN,
105 'locked_by': TEST_USER_ADMIN_LOGIN,
106 'lock_state_changed': True,
106 'lock_state_changed': True,
107 'lock_reason': Repository.LOCK_API,
107 'lock_reason': Repository.LOCK_API,
108 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
108 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
109 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False))
109 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, False))
110 }
110 }
111 assert_ok(id_, expected, given=response.body)
111 assert_ok(id_, expected, given=response.body)
112
112
113 def test_api_lock_repo_lock_aquire_optional_userid(self, backend):
113 def test_api_lock_repo_lock_aquire_optional_userid(self, backend):
114 id_, params = build_data(
114 id_, params = build_data(
115 self.apikey, 'lock',
115 self.apikey, 'lock',
116 repoid=backend.repo_name,
116 repoid=backend.repo_name,
117 locked=True)
117 locked=True)
118 response = api_call(self.app, params)
118 response = api_call(self.app, params)
119 time_ = response.json['result']['locked_since']
119 time_ = response.json['result']['locked_since']
120 expected = {
120 expected = {
121 'repo': backend.repo_name,
121 'repo': backend.repo_name,
122 'locked': True,
122 'locked': True,
123 'locked_since': time_,
123 'locked_since': time_,
124 'locked_by': TEST_USER_ADMIN_LOGIN,
124 'locked_by': TEST_USER_ADMIN_LOGIN,
125 'lock_state_changed': True,
125 'lock_state_changed': True,
126 'lock_reason': Repository.LOCK_API,
126 'lock_reason': Repository.LOCK_API,
127 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
127 'msg': ('User `%s` set lock state for repo `%s` to `%s`'
128 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
128 % (TEST_USER_ADMIN_LOGIN, backend.repo_name, True))
129 }
129 }
130
130
131 assert_ok(id_, expected, given=response.body)
131 assert_ok(id_, expected, given=response.body)
132
132
133 def test_api_lock_repo_lock_optional_locked(self, backend):
133 def test_api_lock_repo_lock_optional_locked(self, backend):
134 # TODO: Provide a fixture locked_repository or similar
134 # TODO: Provide a fixture locked_repository or similar
135 repo = Repository.get_by_repo_name(backend.repo_name)
135 repo = Repository.get_by_repo_name(backend.repo_name)
136 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
136 user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
137 Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API)
137 Repository.lock(repo, user.user_id, lock_reason=Repository.LOCK_API)
138
138
139 id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name)
139 id_, params = build_data(self.apikey, 'lock', repoid=backend.repo_name)
140 response = api_call(self.app, params)
140 response = api_call(self.app, params)
141 time_ = response.json['result']['locked_since']
141 time_ = response.json['result']['locked_since']
142 expected = {
142 expected = {
143 'repo': backend.repo_name,
143 'repo': backend.repo_name,
144 'locked': True,
144 'locked': True,
145 'locked_since': time_,
145 'locked_since': time_,
146 'locked_by': TEST_USER_ADMIN_LOGIN,
146 'locked_by': TEST_USER_ADMIN_LOGIN,
147 'lock_state_changed': False,
147 'lock_state_changed': False,
148 'lock_reason': Repository.LOCK_API,
148 'lock_reason': Repository.LOCK_API,
149 'msg': ('Repo `%s` locked by `%s` on `%s`.'
149 'msg': ('Repo `%s` locked by `%s` on `%s`.'
150 % (backend.repo_name, TEST_USER_ADMIN_LOGIN,
150 % (backend.repo_name, TEST_USER_ADMIN_LOGIN,
151 json.dumps(time_to_datetime(time_))))
151 json.dumps(time_to_datetime(time_))))
152 }
152 }
153 assert_ok(id_, expected, given=response.body)
153 assert_ok(id_, expected, given=response.body)
154
154
155 def test_api_lock_repo_lock_optional_not_locked(self, backend):
155 def test_api_lock_repo_lock_optional_not_locked(self, backend):
156 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
156 repo = backend.create_repo(cur_user=self.TEST_USER_LOGIN)
157 repo_name = repo.repo_name
157 repo_name = repo.repo_name
158 assert repo.locked == [None, None, None]
158 assert repo.locked == [None, None, None]
159 id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id)
159 id_, params = build_data(self.apikey, 'lock', repoid=repo.repo_id)
160 response = api_call(self.app, params)
160 response = api_call(self.app, params)
161 expected = {
161 expected = {
162 'repo': repo_name,
162 'repo': repo_name,
163 'locked': False,
163 'locked': False,
164 'locked_since': None,
164 'locked_since': None,
165 'locked_by': None,
165 'locked_by': None,
166 'lock_state_changed': False,
166 'lock_state_changed': False,
167 'lock_reason': None,
167 'lock_reason': None,
168 'msg': ('Repo `%s` not locked.' % (repo_name,))
168 'msg': ('Repo `%s` not locked.' % (repo_name,))
169 }
169 }
170 assert_ok(id_, expected, given=response.body)
170 assert_ok(id_, expected, given=response.body)
171
171
172 @mock.patch.object(Repository, 'lock', crash)
172 @mock.patch.object(Repository, 'lock', crash)
173 def test_api_lock_error(self, backend):
173 def test_api_lock_error(self, backend):
174 id_, params = build_data(
174 id_, params = build_data(
175 self.apikey, 'lock',
175 self.apikey, 'lock',
176 userid=TEST_USER_ADMIN_LOGIN,
176 userid=TEST_USER_ADMIN_LOGIN,
177 repoid=backend.repo_name,
177 repoid=backend.repo_name,
178 locked=True)
178 locked=True)
179 response = api_call(self.app, params)
179 response = api_call(self.app, params)
180
180
181 expected = 'Error occurred locking repository `%s`' % (
181 expected = 'Error occurred locking repository `%s`' % (
182 backend.repo_name,)
182 backend.repo_name,)
183 assert_error(id_, expected, given=response.body)
183 assert_error(id_, expected, given=response.body)
@@ -1,43 +1,43 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.scm import ScmModel
23 from rhodecode.model.scm import ScmModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_ok, assert_error, crash)
25 build_data, api_call, assert_ok, assert_error, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestRescanRepos(object):
29 class TestRescanRepos(object):
30 def test_api_rescan_repos(self):
30 def test_api_rescan_repos(self):
31 id_, params = build_data(self.apikey, 'rescan_repos')
31 id_, params = build_data(self.apikey, 'rescan_repos')
32 response = api_call(self.app, params)
32 response = api_call(self.app, params)
33
33
34 expected = {'added': [], 'removed': []}
34 expected = {'added': [], 'removed': []}
35 assert_ok(id_, expected, given=response.body)
35 assert_ok(id_, expected, given=response.body)
36
36
37 @mock.patch.object(ScmModel, 'repo_scan', crash)
37 @mock.patch.object(ScmModel, 'repo_scan', crash)
38 def test_api_rescann_error(self):
38 def test_api_rescann_error(self):
39 id_, params = build_data(self.apikey, 'rescan_repos', )
39 id_, params = build_data(self.apikey, 'rescan_repos', )
40 response = api_call(self.app, params)
40 response = api_call(self.app, params)
41
41
42 expected = 'Error occurred during rescan repositories action'
42 expected = 'Error occurred during rescan repositories action'
43 assert_error(id_, expected, given=response.body)
43 assert_error(id_, expected, given=response.body)
@@ -1,66 +1,66 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo import RepoModel
23 from rhodecode.model.repo import RepoModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestRevokeUserGroupPermission(object):
29 class TestRevokeUserGroupPermission(object):
30 def test_api_revoke_user_group_permission(self, backend, user_util):
30 def test_api_revoke_user_group_permission(self, backend, user_util):
31 repo = backend.create_repo()
31 repo = backend.create_repo()
32 user_group = user_util.create_user_group()
32 user_group = user_util.create_user_group()
33 user_util.grant_user_group_permission_to_repo(
33 user_util.grant_user_group_permission_to_repo(
34 repo, user_group, 'repository.read')
34 repo, user_group, 'repository.read')
35 id_, params = build_data(
35 id_, params = build_data(
36 self.apikey,
36 self.apikey,
37 'revoke_user_group_permission',
37 'revoke_user_group_permission',
38 repoid=backend.repo_name,
38 repoid=backend.repo_name,
39 usergroupid=user_group.users_group_name)
39 usergroupid=user_group.users_group_name)
40 response = api_call(self.app, params)
40 response = api_call(self.app, params)
41
41
42 expected = {
42 expected = {
43 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
43 'msg': 'Revoked perm for user group: `%s` in repo: `%s`' % (
44 user_group.users_group_name, backend.repo_name
44 user_group.users_group_name, backend.repo_name
45 ),
45 ),
46 'success': True
46 'success': True
47 }
47 }
48 assert_ok(id_, expected, given=response.body)
48 assert_ok(id_, expected, given=response.body)
49
49
50 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
50 @mock.patch.object(RepoModel, 'revoke_user_group_permission', crash)
51 def test_api_revoke_user_group_permission_exception_when_adding(
51 def test_api_revoke_user_group_permission_exception_when_adding(
52 self, backend, user_util):
52 self, backend, user_util):
53 user_group = user_util.create_user_group()
53 user_group = user_util.create_user_group()
54 id_, params = build_data(
54 id_, params = build_data(
55 self.apikey,
55 self.apikey,
56 'revoke_user_group_permission',
56 'revoke_user_group_permission',
57 repoid=backend.repo_name,
57 repoid=backend.repo_name,
58 usergroupid=user_group.users_group_name)
58 usergroupid=user_group.users_group_name)
59 response = api_call(self.app, params)
59 response = api_call(self.app, params)
60
60
61 expected = (
61 expected = (
62 'failed to edit permission for user group: `%s` in repo: `%s`' % (
62 'failed to edit permission for user group: `%s` in repo: `%s`' % (
63 user_group.users_group_name, backend.repo_name
63 user_group.users_group_name, backend.repo_name
64 )
64 )
65 )
65 )
66 assert_error(id_, expected, given=response.body)
66 assert_error(id_, expected, given=response.body)
@@ -1,128 +1,128 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo_group import RepoGroupModel
23 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
24 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.api.tests.utils import (
25 from rhodecode.api.tests.utils import (
26 build_data, api_call, assert_error, assert_ok, crash)
26 build_data, api_call, assert_error, assert_ok, crash)
27
27
28
28
29 @pytest.mark.usefixtures("testuser_api", "app")
29 @pytest.mark.usefixtures("testuser_api", "app")
30 class TestRevokeUserGroupPermissionFromRepoGroup(object):
30 class TestRevokeUserGroupPermissionFromRepoGroup(object):
31 @pytest.mark.parametrize("name, apply_to_children", [
31 @pytest.mark.parametrize("name, apply_to_children", [
32 ('none', 'none'),
32 ('none', 'none'),
33 ('all', 'all'),
33 ('all', 'all'),
34 ('repos', 'repos'),
34 ('repos', 'repos'),
35 ('groups', 'groups'),
35 ('groups', 'groups'),
36 ])
36 ])
37 def test_api_revoke_user_group_permission_from_repo_group(
37 def test_api_revoke_user_group_permission_from_repo_group(
38 self, name, apply_to_children, user_util):
38 self, name, apply_to_children, user_util):
39 user_group = user_util.create_user_group()
39 user_group = user_util.create_user_group()
40 repo_group = user_util.create_repo_group()
40 repo_group = user_util.create_repo_group()
41 user_util.grant_user_group_permission_to_repo_group(
41 user_util.grant_user_group_permission_to_repo_group(
42 repo_group, user_group, 'group.read')
42 repo_group, user_group, 'group.read')
43
43
44 id_, params = build_data(
44 id_, params = build_data(
45 self.apikey, 'revoke_user_group_permission_from_repo_group',
45 self.apikey, 'revoke_user_group_permission_from_repo_group',
46 repogroupid=repo_group.name,
46 repogroupid=repo_group.name,
47 usergroupid=user_group.users_group_name,
47 usergroupid=user_group.users_group_name,
48 apply_to_children=apply_to_children,)
48 apply_to_children=apply_to_children,)
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
50
51 expected = {
51 expected = {
52 'msg': (
52 'msg': (
53 'Revoked perm (recursive:%s) for user group: `%s`'
53 'Revoked perm (recursive:%s) for user group: `%s`'
54 ' in repo group: `%s`' % (
54 ' in repo group: `%s`' % (
55 apply_to_children, user_group.users_group_name,
55 apply_to_children, user_group.users_group_name,
56 repo_group.name
56 repo_group.name
57 )
57 )
58 ),
58 ),
59 'success': True
59 'success': True
60 }
60 }
61 assert_ok(id_, expected, given=response.body)
61 assert_ok(id_, expected, given=response.body)
62
62
63 @pytest.mark.parametrize(
63 @pytest.mark.parametrize(
64 "name, apply_to_children, grant_admin, access_ok", [
64 "name, apply_to_children, grant_admin, access_ok", [
65 ('none', 'none', False, False),
65 ('none', 'none', False, False),
66 ('all', 'all', False, False),
66 ('all', 'all', False, False),
67 ('repos', 'repos', False, False),
67 ('repos', 'repos', False, False),
68 ('groups', 'groups', False, False),
68 ('groups', 'groups', False, False),
69
69
70 # after granting admin rights
70 # after granting admin rights
71 ('none', 'none', False, False),
71 ('none', 'none', False, False),
72 ('all', 'all', False, False),
72 ('all', 'all', False, False),
73 ('repos', 'repos', False, False),
73 ('repos', 'repos', False, False),
74 ('groups', 'groups', False, False),
74 ('groups', 'groups', False, False),
75 ]
75 ]
76 )
76 )
77 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
77 def test_api_revoke_user_group_permission_from_repo_group_by_regular_user(
78 self, name, apply_to_children, grant_admin, access_ok, user_util):
78 self, name, apply_to_children, grant_admin, access_ok, user_util):
79 user_group = user_util.create_user_group()
79 user_group = user_util.create_user_group()
80 repo_group = user_util.create_repo_group()
80 repo_group = user_util.create_repo_group()
81 user_util.grant_user_group_permission_to_repo_group(
81 user_util.grant_user_group_permission_to_repo_group(
82 repo_group, user_group, 'group.read')
82 repo_group, user_group, 'group.read')
83
83
84 if grant_admin:
84 if grant_admin:
85 user_util.grant_user_permission_to_repo_group(
85 user_util.grant_user_permission_to_repo_group(
86 repo_group.name, self.TEST_USER_LOGIN, 'group.admin')
86 repo_group.name, self.TEST_USER_LOGIN, 'group.admin')
87
87
88 id_, params = build_data(
88 id_, params = build_data(
89 self.apikey_regular,
89 self.apikey_regular,
90 'revoke_user_group_permission_from_repo_group',
90 'revoke_user_group_permission_from_repo_group',
91 repogroupid=repo_group.name,
91 repogroupid=repo_group.name,
92 usergroupid=user_group.users_group_name,
92 usergroupid=user_group.users_group_name,
93 apply_to_children=apply_to_children,)
93 apply_to_children=apply_to_children,)
94 response = api_call(self.app, params)
94 response = api_call(self.app, params)
95 if access_ok:
95 if access_ok:
96 expected = {
96 expected = {
97 'msg': (
97 'msg': (
98 'Revoked perm (recursive:%s) for user group: `%s`'
98 'Revoked perm (recursive:%s) for user group: `%s`'
99 ' in repo group: `%s`' % (
99 ' in repo group: `%s`' % (
100 apply_to_children, TEST_USER_ADMIN_LOGIN,
100 apply_to_children, TEST_USER_ADMIN_LOGIN,
101 repo_group.name
101 repo_group.name
102 )
102 )
103 ),
103 ),
104 'success': True
104 'success': True
105 }
105 }
106 assert_ok(id_, expected, given=response.body)
106 assert_ok(id_, expected, given=response.body)
107 else:
107 else:
108 expected = 'repository group `%s` does not exist' % (
108 expected = 'repository group `%s` does not exist' % (
109 repo_group.name,)
109 repo_group.name,)
110 assert_error(id_, expected, given=response.body)
110 assert_error(id_, expected, given=response.body)
111
111
112 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
112 @mock.patch.object(RepoGroupModel, 'revoke_user_group_permission', crash)
113 def test_api_revoke_user_group_permission_from_repo_group_exception_on_add(
113 def test_api_revoke_user_group_permission_from_repo_group_exception_on_add(
114 self, user_util):
114 self, user_util):
115 user_group = user_util.create_user_group()
115 user_group = user_util.create_user_group()
116 repo_group = user_util.create_repo_group()
116 repo_group = user_util.create_repo_group()
117 id_, params = build_data(
117 id_, params = build_data(
118 self.apikey, 'revoke_user_group_permission_from_repo_group',
118 self.apikey, 'revoke_user_group_permission_from_repo_group',
119 repogroupid=repo_group.name,
119 repogroupid=repo_group.name,
120 usergroupid=user_group.users_group_name)
120 usergroupid=user_group.users_group_name)
121 response = api_call(self.app, params)
121 response = api_call(self.app, params)
122
122
123 expected = (
123 expected = (
124 'failed to edit permission for user group: `%s`'
124 'failed to edit permission for user group: `%s`'
125 ' in repo group: `%s`' % (
125 ' in repo group: `%s`' % (
126 user_group.users_group_name, repo_group.name)
126 user_group.users_group_name, repo_group.name)
127 )
127 )
128 assert_error(id_, expected, given=response.body)
128 assert_error(id_, expected, given=response.body)
@@ -1,57 +1,57 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.model.user import UserModel
22 from rhodecode.model.user import UserModel
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok
24
24
25
25
26 @pytest.mark.usefixtures("testuser_api", "app")
26 @pytest.mark.usefixtures("testuser_api", "app")
27 class TestRevokeUserGroupPermissionFromUserGroup(object):
27 class TestRevokeUserGroupPermissionFromUserGroup(object):
28 @pytest.mark.parametrize("name", [
28 @pytest.mark.parametrize("name", [
29 ('none',),
29 ('none',),
30 ('all',),
30 ('all',),
31 ('repos',),
31 ('repos',),
32 ('groups',),
32 ('groups',),
33 ])
33 ])
34 def test_api_revoke_user_group_permission_from_user_group(
34 def test_api_revoke_user_group_permission_from_user_group(
35 self, name, user_util):
35 self, name, user_util):
36 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
36 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
37 group = user_util.create_user_group()
37 group = user_util.create_user_group()
38 source_group = user_util.create_user_group()
38 source_group = user_util.create_user_group()
39
39
40 user_util.grant_user_permission_to_user_group(
40 user_util.grant_user_permission_to_user_group(
41 group, user, 'usergroup.read')
41 group, user, 'usergroup.read')
42 user_util.grant_user_group_permission_to_user_group(
42 user_util.grant_user_group_permission_to_user_group(
43 source_group, group, 'usergroup.read')
43 source_group, group, 'usergroup.read')
44
44
45 id_, params = build_data(
45 id_, params = build_data(
46 self.apikey, 'revoke_user_group_permission_from_user_group',
46 self.apikey, 'revoke_user_group_permission_from_user_group',
47 usergroupid=group.users_group_name,
47 usergroupid=group.users_group_name,
48 sourceusergroupid=source_group.users_group_name)
48 sourceusergroupid=source_group.users_group_name)
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
50
51 expected = {
51 expected = {
52 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % (
52 'msg': 'Revoked perm for user group: `%s` in user group: `%s`' % (
53 source_group.users_group_name, group.users_group_name
53 source_group.users_group_name, group.users_group_name
54 ),
54 ),
55 'success': True
55 'success': True
56 }
56 }
57 assert_ok(id_, expected, given=response.body)
57 assert_ok(id_, expected, given=response.body)
@@ -1,65 +1,65 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo import RepoModel
23 from rhodecode.model.repo import RepoModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestRevokeUserPermission(object):
29 class TestRevokeUserPermission(object):
30 def test_api_revoke_user_permission(self, backend, user_util):
30 def test_api_revoke_user_permission(self, backend, user_util):
31 repo = backend.create_repo()
31 repo = backend.create_repo()
32 user = user_util.create_user()
32 user = user_util.create_user()
33 user_util.grant_user_permission_to_repo(
33 user_util.grant_user_permission_to_repo(
34 repo, user, 'repository.read')
34 repo, user, 'repository.read')
35
35
36 id_, params = build_data(
36 id_, params = build_data(
37 self.apikey,
37 self.apikey,
38 'revoke_user_permission',
38 'revoke_user_permission',
39 repoid=repo.repo_name,
39 repoid=repo.repo_name,
40 userid=user.username)
40 userid=user.username)
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = {
43 expected = {
44 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
44 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % (
45 user.username, backend.repo_name
45 user.username, backend.repo_name
46 ),
46 ),
47 'success': True
47 'success': True
48 }
48 }
49 assert_ok(id_, expected, given=response.body)
49 assert_ok(id_, expected, given=response.body)
50
50
51 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
51 @mock.patch.object(RepoModel, 'revoke_user_permission', crash)
52 def test_api_revoke_user_permission_exception_when_adding(
52 def test_api_revoke_user_permission_exception_when_adding(
53 self, backend, user_util):
53 self, backend, user_util):
54 user = user_util.create_user()
54 user = user_util.create_user()
55 id_, params = build_data(
55 id_, params = build_data(
56 self.apikey,
56 self.apikey,
57 'revoke_user_permission',
57 'revoke_user_permission',
58 repoid=backend.repo_name,
58 repoid=backend.repo_name,
59 userid=user.username)
59 userid=user.username)
60 response = api_call(self.app, params)
60 response = api_call(self.app, params)
61
61
62 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
62 expected = 'failed to edit permission for user: `%s` in repo: `%s`' % (
63 user.username, backend.repo_name
63 user.username, backend.repo_name
64 )
64 )
65 assert_error(id_, expected, given=response.body)
65 assert_error(id_, expected, given=response.body)
@@ -1,125 +1,125 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo_group import RepoGroupModel
23 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestRevokeUserPermissionFromRepoGroup(object):
29 class TestRevokeUserPermissionFromRepoGroup(object):
30 @pytest.mark.parametrize("name, apply_to_children", [
30 @pytest.mark.parametrize("name, apply_to_children", [
31 ('none', 'none'),
31 ('none', 'none'),
32 ('all', 'all'),
32 ('all', 'all'),
33 ('repos', 'repos'),
33 ('repos', 'repos'),
34 ('groups', 'groups'),
34 ('groups', 'groups'),
35 ])
35 ])
36 def test_api_revoke_user_permission_from_repo_group(
36 def test_api_revoke_user_permission_from_repo_group(
37 self, name, apply_to_children, user_util):
37 self, name, apply_to_children, user_util):
38 user = user_util.create_user()
38 user = user_util.create_user()
39 repo_group = user_util.create_repo_group()
39 repo_group = user_util.create_repo_group()
40 user_util.grant_user_permission_to_repo_group(
40 user_util.grant_user_permission_to_repo_group(
41 repo_group, user, 'group.read')
41 repo_group, user, 'group.read')
42
42
43 id_, params = build_data(
43 id_, params = build_data(
44 self.apikey,
44 self.apikey,
45 'revoke_user_permission_from_repo_group',
45 'revoke_user_permission_from_repo_group',
46 repogroupid=repo_group.name,
46 repogroupid=repo_group.name,
47 userid=user.username,
47 userid=user.username,
48 apply_to_children=apply_to_children,)
48 apply_to_children=apply_to_children,)
49 response = api_call(self.app, params)
49 response = api_call(self.app, params)
50
50
51 expected = {
51 expected = {
52 'msg': (
52 'msg': (
53 'Revoked perm (recursive:%s) for user: `%s`'
53 'Revoked perm (recursive:%s) for user: `%s`'
54 ' in repo group: `%s`' % (
54 ' in repo group: `%s`' % (
55 apply_to_children, user.username, repo_group.name
55 apply_to_children, user.username, repo_group.name
56 )
56 )
57 ),
57 ),
58 'success': True
58 'success': True
59 }
59 }
60 assert_ok(id_, expected, given=response.body)
60 assert_ok(id_, expected, given=response.body)
61
61
62 @pytest.mark.parametrize(
62 @pytest.mark.parametrize(
63 "name, apply_to_children, grant_admin, access_ok", [
63 "name, apply_to_children, grant_admin, access_ok", [
64 ('none', 'none', False, False),
64 ('none', 'none', False, False),
65 ('all', 'all', False, False),
65 ('all', 'all', False, False),
66 ('repos', 'repos', False, False),
66 ('repos', 'repos', False, False),
67 ('groups', 'groups', False, False),
67 ('groups', 'groups', False, False),
68
68
69 # after granting admin rights
69 # after granting admin rights
70 ('none', 'none', False, False),
70 ('none', 'none', False, False),
71 ('all', 'all', False, False),
71 ('all', 'all', False, False),
72 ('repos', 'repos', False, False),
72 ('repos', 'repos', False, False),
73 ('groups', 'groups', False, False),
73 ('groups', 'groups', False, False),
74 ]
74 ]
75 )
75 )
76 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
76 def test_api_revoke_user_permission_from_repo_group_by_regular_user(
77 self, name, apply_to_children, grant_admin, access_ok, user_util):
77 self, name, apply_to_children, grant_admin, access_ok, user_util):
78 user = user_util.create_user()
78 user = user_util.create_user()
79 repo_group = user_util.create_repo_group()
79 repo_group = user_util.create_repo_group()
80 permission = 'group.admin' if grant_admin else 'group.read'
80 permission = 'group.admin' if grant_admin else 'group.read'
81 user_util.grant_user_permission_to_repo_group(
81 user_util.grant_user_permission_to_repo_group(
82 repo_group, user, permission)
82 repo_group, user, permission)
83
83
84 id_, params = build_data(
84 id_, params = build_data(
85 self.apikey_regular,
85 self.apikey_regular,
86 'revoke_user_permission_from_repo_group',
86 'revoke_user_permission_from_repo_group',
87 repogroupid=repo_group.name,
87 repogroupid=repo_group.name,
88 userid=user.username,
88 userid=user.username,
89 apply_to_children=apply_to_children,)
89 apply_to_children=apply_to_children,)
90 response = api_call(self.app, params)
90 response = api_call(self.app, params)
91 if access_ok:
91 if access_ok:
92 expected = {
92 expected = {
93 'msg': (
93 'msg': (
94 'Revoked perm (recursive:%s) for user: `%s`'
94 'Revoked perm (recursive:%s) for user: `%s`'
95 ' in repo group: `%s`' % (
95 ' in repo group: `%s`' % (
96 apply_to_children, user.username, repo_group.name
96 apply_to_children, user.username, repo_group.name
97 )
97 )
98 ),
98 ),
99 'success': True
99 'success': True
100 }
100 }
101 assert_ok(id_, expected, given=response.body)
101 assert_ok(id_, expected, given=response.body)
102 else:
102 else:
103 expected = 'repository group `%s` does not exist' % (
103 expected = 'repository group `%s` does not exist' % (
104 repo_group.name)
104 repo_group.name)
105 assert_error(id_, expected, given=response.body)
105 assert_error(id_, expected, given=response.body)
106
106
107 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
107 @mock.patch.object(RepoGroupModel, 'revoke_user_permission', crash)
108 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(
108 def test_api_revoke_user_permission_from_repo_group_exception_when_adding(
109 self, user_util):
109 self, user_util):
110 user = user_util.create_user()
110 user = user_util.create_user()
111 repo_group = user_util.create_repo_group()
111 repo_group = user_util.create_repo_group()
112 id_, params = build_data(
112 id_, params = build_data(
113 self.apikey,
113 self.apikey,
114 'revoke_user_permission_from_repo_group',
114 'revoke_user_permission_from_repo_group',
115 repogroupid=repo_group.name,
115 repogroupid=repo_group.name,
116 userid=user.username
116 userid=user.username
117 )
117 )
118 response = api_call(self.app, params)
118 response = api_call(self.app, params)
119
119
120 expected = (
120 expected = (
121 'failed to edit permission for user: `%s` in repo group: `%s`' % (
121 'failed to edit permission for user: `%s` in repo group: `%s`' % (
122 user.username, repo_group.name
122 user.username, repo_group.name
123 )
123 )
124 )
124 )
125 assert_error(id_, expected, given=response.body)
125 assert_error(id_, expected, given=response.body)
@@ -1,111 +1,111 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user_group import UserGroupModel
23 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.api.tests.utils import (
24 from rhodecode.api.tests.utils import (
25 build_data, api_call, assert_error, assert_ok, crash)
25 build_data, api_call, assert_error, assert_ok, crash)
26
26
27
27
28 @pytest.mark.usefixtures("testuser_api", "app")
28 @pytest.mark.usefixtures("testuser_api", "app")
29 class TestRevokeUserPermissionFromUserGroup(object):
29 class TestRevokeUserPermissionFromUserGroup(object):
30 @pytest.mark.parametrize("name", [
30 @pytest.mark.parametrize("name", [
31 ('none',),
31 ('none',),
32 ('all',),
32 ('all',),
33 ('repos',),
33 ('repos',),
34 ('groups',),
34 ('groups',),
35 ])
35 ])
36 def test_api_revoke_user_permission_from_user_group(self, name, user_util):
36 def test_api_revoke_user_permission_from_user_group(self, name, user_util):
37 user = user_util.create_user()
37 user = user_util.create_user()
38 group = user_util.create_user_group()
38 group = user_util.create_user_group()
39 user_util.grant_user_permission_to_user_group(
39 user_util.grant_user_permission_to_user_group(
40 group, user, 'usergroup.admin')
40 group, user, 'usergroup.admin')
41
41
42 id_, params = build_data(
42 id_, params = build_data(
43 self.apikey,
43 self.apikey,
44 'revoke_user_permission_from_user_group',
44 'revoke_user_permission_from_user_group',
45 usergroupid=group.users_group_name,
45 usergroupid=group.users_group_name,
46 userid=user.username)
46 userid=user.username)
47 response = api_call(self.app, params)
47 response = api_call(self.app, params)
48
48
49 expected = {
49 expected = {
50 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
50 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
51 user.username, group.users_group_name
51 user.username, group.users_group_name
52 ),
52 ),
53 'success': True
53 'success': True
54 }
54 }
55 assert_ok(id_, expected, given=response.body)
55 assert_ok(id_, expected, given=response.body)
56
56
57 @pytest.mark.parametrize("name, grant_admin, access_ok", [
57 @pytest.mark.parametrize("name, grant_admin, access_ok", [
58 ('none', False, False),
58 ('none', False, False),
59 ('all', False, False),
59 ('all', False, False),
60 ('repos', False, False),
60 ('repos', False, False),
61 ('groups', False, False),
61 ('groups', False, False),
62
62
63 # after granting admin rights
63 # after granting admin rights
64 ('none', False, False),
64 ('none', False, False),
65 ('all', False, False),
65 ('all', False, False),
66 ('repos', False, False),
66 ('repos', False, False),
67 ('groups', False, False),
67 ('groups', False, False),
68 ])
68 ])
69 def test_api_revoke_user_permission_from_user_group_by_regular_user(
69 def test_api_revoke_user_permission_from_user_group_by_regular_user(
70 self, name, grant_admin, access_ok, user_util):
70 self, name, grant_admin, access_ok, user_util):
71 user = user_util.create_user()
71 user = user_util.create_user()
72 group = user_util.create_user_group()
72 group = user_util.create_user_group()
73 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
73 permission = 'usergroup.admin' if grant_admin else 'usergroup.read'
74 user_util.grant_user_permission_to_user_group(group, user, permission)
74 user_util.grant_user_permission_to_user_group(group, user, permission)
75
75
76 id_, params = build_data(
76 id_, params = build_data(
77 self.apikey_regular,
77 self.apikey_regular,
78 'revoke_user_permission_from_user_group',
78 'revoke_user_permission_from_user_group',
79 usergroupid=group.users_group_name,
79 usergroupid=group.users_group_name,
80 userid=user.username)
80 userid=user.username)
81 response = api_call(self.app, params)
81 response = api_call(self.app, params)
82 if access_ok:
82 if access_ok:
83 expected = {
83 expected = {
84 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
84 'msg': 'Revoked perm for user: `%s` in user group: `%s`' % (
85 user.username, group.users_group_name
85 user.username, group.users_group_name
86 ),
86 ),
87 'success': True
87 'success': True
88 }
88 }
89 assert_ok(id_, expected, given=response.body)
89 assert_ok(id_, expected, given=response.body)
90 else:
90 else:
91 expected = 'user group `%s` does not exist' % (
91 expected = 'user group `%s` does not exist' % (
92 group.users_group_name)
92 group.users_group_name)
93 assert_error(id_, expected, given=response.body)
93 assert_error(id_, expected, given=response.body)
94
94
95 @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash)
95 @mock.patch.object(UserGroupModel, 'revoke_user_permission', crash)
96 def test_api_revoke_user_permission_from_user_group_exception_when_adding(
96 def test_api_revoke_user_permission_from_user_group_exception_when_adding(
97 self, user_util):
97 self, user_util):
98 user = user_util.create_user()
98 user = user_util.create_user()
99 group = user_util.create_user_group()
99 group = user_util.create_user_group()
100 id_, params = build_data(
100 id_, params = build_data(
101 self.apikey,
101 self.apikey,
102 'revoke_user_permission_from_user_group',
102 'revoke_user_permission_from_user_group',
103 usergroupid=group.users_group_name,
103 usergroupid=group.users_group_name,
104 userid=user.username)
104 userid=user.username)
105 response = api_call(self.app, params)
105 response = api_call(self.app, params)
106
106
107 expected = (
107 expected = (
108 'failed to edit permission for user: `%s` in user group: `%s`' % (
108 'failed to edit permission for user: `%s` in user group: `%s`' % (
109 user.username, group.users_group_name)
109 user.username, group.users_group_name)
110 )
110 )
111 assert_error(id_, expected, given=response.body)
111 assert_error(id_, expected, given=response.body)
@@ -1,58 +1,58 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, assert_error
23 from rhodecode.api.tests.utils import build_data, api_call, assert_ok, assert_error
24
24
25
25
26 @pytest.mark.usefixtures("testuser_api", "app")
26 @pytest.mark.usefixtures("testuser_api", "app")
27 class TestStoreException(object):
27 class TestStoreException(object):
28
28
29 def test_store_exception_invalid_json(self):
29 def test_store_exception_invalid_json(self):
30 id_, params = build_data(self.apikey, 'store_exception',
30 id_, params = build_data(self.apikey, 'store_exception',
31 exc_data_json='XXX,{')
31 exc_data_json='XXX,{')
32 response = api_call(self.app, params)
32 response = api_call(self.app, params)
33
33
34 expected = 'Failed to parse JSON data from exc_data_json field. ' \
34 expected = 'Failed to parse JSON data from exc_data_json field. ' \
35 'Please make sure it contains a valid JSON.'
35 'Please make sure it contains a valid JSON.'
36 assert_error(id_, expected, given=response.body)
36 assert_error(id_, expected, given=response.body)
37
37
38 def test_store_exception_missing_json_params_json(self):
38 def test_store_exception_missing_json_params_json(self):
39 id_, params = build_data(self.apikey, 'store_exception',
39 id_, params = build_data(self.apikey, 'store_exception',
40 exc_data_json='{"foo":"bar"}')
40 exc_data_json='{"foo":"bar"}')
41 response = api_call(self.app, params)
41 response = api_call(self.app, params)
42
42
43 expected = "Missing exc_traceback, or exc_type_name in " \
43 expected = "Missing exc_traceback, or exc_type_name in " \
44 "exc_data_json field. Missing: 'exc_traceback'"
44 "exc_data_json field. Missing: 'exc_traceback'"
45 assert_error(id_, expected, given=response.body)
45 assert_error(id_, expected, given=response.body)
46
46
47 def test_store_exception(self):
47 def test_store_exception(self):
48 id_, params = build_data(
48 id_, params = build_data(
49 self.apikey, 'store_exception',
49 self.apikey, 'store_exception',
50 exc_data_json='{"exc_traceback": "invalid", "exc_type_name":"ValueError"}')
50 exc_data_json='{"exc_traceback": "invalid", "exc_type_name":"ValueError"}')
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52 exc_id = response.json['result']['exc_id']
52 exc_id = response.json['result']['exc_id']
53
53
54 expected = {
54 expected = {
55 'exc_id': exc_id,
55 'exc_id': exc_id,
56 'exc_url': 'http://example.com/_admin/settings/exceptions/{}'.format(exc_id)
56 'exc_url': 'http://example.com/_admin/settings/exceptions/{}'.format(exc_id)
57 }
57 }
58 assert_ok(id_, expected, given=response.body)
58 assert_ok(id_, expected, given=response.body)
@@ -1,214 +1,214 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import pytest
20 import pytest
21
21
22 from rhodecode.lib.vcs.nodes import FileNode
22 from rhodecode.lib.vcs.nodes import FileNode
23 from rhodecode.model.db import User
23 from rhodecode.model.db import User
24 from rhodecode.model.pull_request import PullRequestModel
24 from rhodecode.model.pull_request import PullRequestModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error)
27 build_data, api_call, assert_ok, assert_error)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestUpdatePullRequest(object):
31 class TestUpdatePullRequest(object):
32
32
33 @pytest.mark.backends("git", "hg")
33 @pytest.mark.backends("git", "hg")
34 def test_api_update_pull_request_title_or_description(
34 def test_api_update_pull_request_title_or_description(
35 self, pr_util, no_notifications):
35 self, pr_util, no_notifications):
36 pull_request = pr_util.create_pull_request()
36 pull_request = pr_util.create_pull_request()
37
37
38 id_, params = build_data(
38 id_, params = build_data(
39 self.apikey, 'update_pull_request',
39 self.apikey, 'update_pull_request',
40 repoid=pull_request.target_repo.repo_name,
40 repoid=pull_request.target_repo.repo_name,
41 pullrequestid=pull_request.pull_request_id,
41 pullrequestid=pull_request.pull_request_id,
42 title='New TITLE OF A PR',
42 title='New TITLE OF A PR',
43 description='New DESC OF A PR',
43 description='New DESC OF A PR',
44 )
44 )
45 response = api_call(self.app, params)
45 response = api_call(self.app, params)
46
46
47 expected = {
47 expected = {
48 "msg": "Updated pull request `{}`".format(
48 "msg": "Updated pull request `{}`".format(
49 pull_request.pull_request_id),
49 pull_request.pull_request_id),
50 "pull_request": response.json['result']['pull_request'],
50 "pull_request": response.json['result']['pull_request'],
51 "updated_commits": {"added": [], "common": [], "removed": []},
51 "updated_commits": {"added": [], "common": [], "removed": []},
52 "updated_reviewers": {"added": [], "removed": []},
52 "updated_reviewers": {"added": [], "removed": []},
53 "updated_observers": {"added": [], "removed": []},
53 "updated_observers": {"added": [], "removed": []},
54 }
54 }
55
55
56 response_json = response.json['result']
56 response_json = response.json['result']
57 assert response_json == expected
57 assert response_json == expected
58 pr = response_json['pull_request']
58 pr = response_json['pull_request']
59 assert pr['title'] == 'New TITLE OF A PR'
59 assert pr['title'] == 'New TITLE OF A PR'
60 assert pr['description'] == 'New DESC OF A PR'
60 assert pr['description'] == 'New DESC OF A PR'
61
61
62 @pytest.mark.backends("git", "hg")
62 @pytest.mark.backends("git", "hg")
63 def test_api_try_update_closed_pull_request(
63 def test_api_try_update_closed_pull_request(
64 self, pr_util, no_notifications):
64 self, pr_util, no_notifications):
65 pull_request = pr_util.create_pull_request()
65 pull_request = pr_util.create_pull_request()
66 PullRequestModel().close_pull_request(
66 PullRequestModel().close_pull_request(
67 pull_request, TEST_USER_ADMIN_LOGIN)
67 pull_request, TEST_USER_ADMIN_LOGIN)
68
68
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'update_pull_request',
70 self.apikey, 'update_pull_request',
71 repoid=pull_request.target_repo.repo_name,
71 repoid=pull_request.target_repo.repo_name,
72 pullrequestid=pull_request.pull_request_id)
72 pullrequestid=pull_request.pull_request_id)
73 response = api_call(self.app, params)
73 response = api_call(self.app, params)
74
74
75 expected = 'pull request `{}` update failed, pull request ' \
75 expected = 'pull request `{}` update failed, pull request ' \
76 'is closed'.format(pull_request.pull_request_id)
76 'is closed'.format(pull_request.pull_request_id)
77
77
78 assert_error(id_, expected, response.body)
78 assert_error(id_, expected, response.body)
79
79
80 @pytest.mark.backends("git", "hg")
80 @pytest.mark.backends("git", "hg")
81 def test_api_update_update_commits(self, pr_util, no_notifications):
81 def test_api_update_update_commits(self, pr_util, no_notifications):
82 commits = [
82 commits = [
83 {'message': 'a'},
83 {'message': 'a'},
84 {'message': 'b', 'added': [FileNode(b'file_b', b'test_content\n')]},
84 {'message': 'b', 'added': [FileNode(b'file_b', b'test_content\n')]},
85 {'message': 'c', 'added': [FileNode(b'file_c', b'test_content\n')]},
85 {'message': 'c', 'added': [FileNode(b'file_c', b'test_content\n')]},
86 ]
86 ]
87 pull_request = pr_util.create_pull_request(
87 pull_request = pr_util.create_pull_request(
88 commits=commits, target_head='a', source_head='b', revisions=['b'])
88 commits=commits, target_head='a', source_head='b', revisions=['b'])
89 pr_util.update_source_repository(head='c')
89 pr_util.update_source_repository(head='c')
90 repo = pull_request.source_repo.scm_instance()
90 repo = pull_request.source_repo.scm_instance()
91 commits = [x for x in repo.get_commits()]
91 commits = [x for x in repo.get_commits()]
92
92
93 added_commit_id = commits[-1].raw_id # c commit
93 added_commit_id = commits[-1].raw_id # c commit
94 common_commit_id = commits[1].raw_id # b commit is common ancestor
94 common_commit_id = commits[1].raw_id # b commit is common ancestor
95 total_commits = [added_commit_id, common_commit_id]
95 total_commits = [added_commit_id, common_commit_id]
96
96
97 id_, params = build_data(
97 id_, params = build_data(
98 self.apikey, 'update_pull_request',
98 self.apikey, 'update_pull_request',
99 repoid=pull_request.target_repo.repo_name,
99 repoid=pull_request.target_repo.repo_name,
100 pullrequestid=pull_request.pull_request_id,
100 pullrequestid=pull_request.pull_request_id,
101 update_commits=True
101 update_commits=True
102 )
102 )
103 response = api_call(self.app, params)
103 response = api_call(self.app, params)
104
104
105 expected = {
105 expected = {
106 "msg": "Updated pull request `{}`".format(
106 "msg": "Updated pull request `{}`".format(
107 pull_request.pull_request_id),
107 pull_request.pull_request_id),
108 "pull_request": response.json['result']['pull_request'],
108 "pull_request": response.json['result']['pull_request'],
109 "updated_commits": {"added": [added_commit_id],
109 "updated_commits": {"added": [added_commit_id],
110 "common": [common_commit_id],
110 "common": [common_commit_id],
111 "total": total_commits,
111 "total": total_commits,
112 "removed": []},
112 "removed": []},
113 "updated_reviewers": {"added": [], "removed": []},
113 "updated_reviewers": {"added": [], "removed": []},
114 "updated_observers": {"added": [], "removed": []},
114 "updated_observers": {"added": [], "removed": []},
115 }
115 }
116
116
117 assert_ok(id_, expected, response.body)
117 assert_ok(id_, expected, response.body)
118
118
119 @pytest.mark.backends("git", "hg")
119 @pytest.mark.backends("git", "hg")
120 def test_api_update_change_reviewers(
120 def test_api_update_change_reviewers(
121 self, user_util, pr_util, no_notifications):
121 self, user_util, pr_util, no_notifications):
122 a = user_util.create_user()
122 a = user_util.create_user()
123 b = user_util.create_user()
123 b = user_util.create_user()
124 c = user_util.create_user()
124 c = user_util.create_user()
125 new_reviewers = [
125 new_reviewers = [
126 {'username': b.username, 'reasons': ['updated via API'],
126 {'username': b.username, 'reasons': ['updated via API'],
127 'mandatory':False},
127 'mandatory':False},
128 {'username': c.username, 'reasons': ['updated via API'],
128 {'username': c.username, 'reasons': ['updated via API'],
129 'mandatory':False},
129 'mandatory':False},
130 ]
130 ]
131
131
132 added = [b.username, c.username]
132 added = [b.username, c.username]
133 removed = [a.username]
133 removed = [a.username]
134
134
135 pull_request = pr_util.create_pull_request(
135 pull_request = pr_util.create_pull_request(
136 reviewers=[(a.username, ['added via API'], False, 'reviewer', [])])
136 reviewers=[(a.username, ['added via API'], False, 'reviewer', [])])
137
137
138 id_, params = build_data(
138 id_, params = build_data(
139 self.apikey, 'update_pull_request',
139 self.apikey, 'update_pull_request',
140 repoid=pull_request.target_repo.repo_name,
140 repoid=pull_request.target_repo.repo_name,
141 pullrequestid=pull_request.pull_request_id,
141 pullrequestid=pull_request.pull_request_id,
142 reviewers=new_reviewers)
142 reviewers=new_reviewers)
143 response = api_call(self.app, params)
143 response = api_call(self.app, params)
144 expected = {
144 expected = {
145 "msg": "Updated pull request `{}`".format(
145 "msg": "Updated pull request `{}`".format(
146 pull_request.pull_request_id),
146 pull_request.pull_request_id),
147 "pull_request": response.json['result']['pull_request'],
147 "pull_request": response.json['result']['pull_request'],
148 "updated_commits": {"added": [], "common": [], "removed": []},
148 "updated_commits": {"added": [], "common": [], "removed": []},
149 "updated_reviewers": {"added": added, "removed": removed},
149 "updated_reviewers": {"added": added, "removed": removed},
150 "updated_observers": {"added": [], "removed": []},
150 "updated_observers": {"added": [], "removed": []},
151 }
151 }
152
152
153 assert_ok(id_, expected, response.body)
153 assert_ok(id_, expected, response.body)
154
154
155 @pytest.mark.backends("git", "hg")
155 @pytest.mark.backends("git", "hg")
156 def test_api_update_bad_user_in_reviewers(self, pr_util):
156 def test_api_update_bad_user_in_reviewers(self, pr_util):
157 pull_request = pr_util.create_pull_request()
157 pull_request = pr_util.create_pull_request()
158
158
159 id_, params = build_data(
159 id_, params = build_data(
160 self.apikey, 'update_pull_request',
160 self.apikey, 'update_pull_request',
161 repoid=pull_request.target_repo.repo_name,
161 repoid=pull_request.target_repo.repo_name,
162 pullrequestid=pull_request.pull_request_id,
162 pullrequestid=pull_request.pull_request_id,
163 reviewers=[{'username': 'bad_name'}])
163 reviewers=[{'username': 'bad_name'}])
164 response = api_call(self.app, params)
164 response = api_call(self.app, params)
165
165
166 expected = 'user `bad_name` does not exist'
166 expected = 'user `bad_name` does not exist'
167
167
168 assert_error(id_, expected, response.body)
168 assert_error(id_, expected, response.body)
169
169
170 @pytest.mark.backends("git", "hg")
170 @pytest.mark.backends("git", "hg")
171 def test_api_update_repo_error(self, pr_util):
171 def test_api_update_repo_error(self, pr_util):
172 pull_request = pr_util.create_pull_request()
172 pull_request = pr_util.create_pull_request()
173 id_, params = build_data(
173 id_, params = build_data(
174 self.apikey, 'update_pull_request',
174 self.apikey, 'update_pull_request',
175 repoid='fake',
175 repoid='fake',
176 pullrequestid=pull_request.pull_request_id,
176 pullrequestid=pull_request.pull_request_id,
177 reviewers=[{'username': 'bad_name'}])
177 reviewers=[{'username': 'bad_name'}])
178 response = api_call(self.app, params)
178 response = api_call(self.app, params)
179
179
180 expected = 'repository `fake` does not exist'
180 expected = 'repository `fake` does not exist'
181
181
182 response_json = response.json['error']
182 response_json = response.json['error']
183 assert response_json == expected
183 assert response_json == expected
184
184
185 @pytest.mark.backends("git", "hg")
185 @pytest.mark.backends("git", "hg")
186 def test_api_update_pull_request_error(self, pr_util):
186 def test_api_update_pull_request_error(self, pr_util):
187 pull_request = pr_util.create_pull_request()
187 pull_request = pr_util.create_pull_request()
188
188
189 id_, params = build_data(
189 id_, params = build_data(
190 self.apikey, 'update_pull_request',
190 self.apikey, 'update_pull_request',
191 repoid=pull_request.target_repo.repo_name,
191 repoid=pull_request.target_repo.repo_name,
192 pullrequestid=999999,
192 pullrequestid=999999,
193 reviewers=[{'username': 'bad_name'}])
193 reviewers=[{'username': 'bad_name'}])
194 response = api_call(self.app, params)
194 response = api_call(self.app, params)
195
195
196 expected = 'pull request `999999` does not exist'
196 expected = 'pull request `999999` does not exist'
197 assert_error(id_, expected, response.body)
197 assert_error(id_, expected, response.body)
198
198
199 @pytest.mark.backends("git", "hg")
199 @pytest.mark.backends("git", "hg")
200 def test_api_update_pull_request_no_perms_to_update(
200 def test_api_update_pull_request_no_perms_to_update(
201 self, user_util, pr_util):
201 self, user_util, pr_util):
202 user = user_util.create_user()
202 user = user_util.create_user()
203 pull_request = pr_util.create_pull_request()
203 pull_request = pr_util.create_pull_request()
204
204
205 id_, params = build_data(
205 id_, params = build_data(
206 user.api_key, 'update_pull_request',
206 user.api_key, 'update_pull_request',
207 repoid=pull_request.target_repo.repo_name,
207 repoid=pull_request.target_repo.repo_name,
208 pullrequestid=pull_request.pull_request_id,)
208 pullrequestid=pull_request.pull_request_id,)
209 response = api_call(self.app, params)
209 response = api_call(self.app, params)
210
210
211 expected = ('pull request `%s` update failed, '
211 expected = ('pull request `%s` update failed, '
212 'no permission to update.') % pull_request.pull_request_id
212 'no permission to update.') % pull_request.pull_request_id
213
213
214 assert_error(id_, expected, response.body)
214 assert_error(id_, expected, response.body)
@@ -1,209 +1,209 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.repo import RepoModel
23 from rhodecode.model.repo import RepoModel
24 from rhodecode.model.scm import ScmModel
24 from rhodecode.model.scm import ScmModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN, TEST_USER_REGULAR_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28 from rhodecode.tests.fixture import Fixture
28 from rhodecode.tests.fixture import Fixture
29 from rhodecode.tests.fixture_mods.fixture_utils import plain_http_host_only_stub
29 from rhodecode.tests.fixture_mods.fixture_utils import plain_http_host_only_stub
30
30
31 fixture = Fixture()
31 fixture = Fixture()
32
32
33 UPDATE_REPO_NAME = 'api_update_me'
33 UPDATE_REPO_NAME = 'api_update_me'
34
34
35
35
36 class SAME_AS_UPDATES(object):
36 class SAME_AS_UPDATES(object):
37 """ Constant used for tests below """
37 """ Constant used for tests below """
38
38
39
39
40 @pytest.mark.usefixtures("testuser_api", "app")
40 @pytest.mark.usefixtures("testuser_api", "app")
41 class TestApiUpdateRepo(object):
41 class TestApiUpdateRepo(object):
42
42
43 @pytest.mark.parametrize("updates, expected", [
43 @pytest.mark.parametrize("updates, expected", [
44 ({'owner': TEST_USER_REGULAR_LOGIN},
44 ({'owner': TEST_USER_REGULAR_LOGIN},
45 SAME_AS_UPDATES),
45 SAME_AS_UPDATES),
46
46
47 ({'description': 'new description'},
47 ({'description': 'new description'},
48 SAME_AS_UPDATES),
48 SAME_AS_UPDATES),
49
49
50 ({'clone_uri': 'http://foo.com/repo'},
50 ({'clone_uri': 'http://foo.com/repo'},
51 SAME_AS_UPDATES),
51 SAME_AS_UPDATES),
52
52
53 ({'clone_uri': None},
53 ({'clone_uri': None},
54 {'clone_uri': ''}),
54 {'clone_uri': ''}),
55
55
56 ({'clone_uri': ''},
56 ({'clone_uri': ''},
57 {'clone_uri': ''}),
57 {'clone_uri': ''}),
58
58
59 ({'clone_uri': 'http://example.com/repo_pull'},
59 ({'clone_uri': 'http://example.com/repo_pull'},
60 {'clone_uri': 'http://example.com/repo_pull'}),
60 {'clone_uri': 'http://example.com/repo_pull'}),
61
61
62 ({'push_uri': ''},
62 ({'push_uri': ''},
63 {'push_uri': ''}),
63 {'push_uri': ''}),
64
64
65 ({'push_uri': 'http://example.com/repo_push'},
65 ({'push_uri': 'http://example.com/repo_push'},
66 {'push_uri': 'http://example.com/repo_push'}),
66 {'push_uri': 'http://example.com/repo_push'}),
67
67
68 ({'landing_rev': None}, # auto-updated based on type of repo
68 ({'landing_rev': None}, # auto-updated based on type of repo
69 {'landing_rev': [None, None]}),
69 {'landing_rev': [None, None]}),
70
70
71 ({'enable_statistics': True},
71 ({'enable_statistics': True},
72 SAME_AS_UPDATES),
72 SAME_AS_UPDATES),
73
73
74 ({'enable_locking': True},
74 ({'enable_locking': True},
75 SAME_AS_UPDATES),
75 SAME_AS_UPDATES),
76
76
77 ({'enable_downloads': True},
77 ({'enable_downloads': True},
78 SAME_AS_UPDATES),
78 SAME_AS_UPDATES),
79
79
80 ({'repo_name': 'new_repo_name'},
80 ({'repo_name': 'new_repo_name'},
81 {
81 {
82 'repo_name': 'new_repo_name',
82 'repo_name': 'new_repo_name',
83 'url': 'http://{}/new_repo_name'.format(plain_http_host_only_stub())
83 'url': 'http://{}/new_repo_name'.format(plain_http_host_only_stub())
84 }),
84 }),
85
85
86 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
86 ({'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
87 '_group': 'test_group_for_update'},
87 '_group': 'test_group_for_update'},
88 {
88 {
89 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
89 'repo_name': 'test_group_for_update/{}'.format(UPDATE_REPO_NAME),
90 'url': 'http://{}/test_group_for_update/{}'.format(
90 'url': 'http://{}/test_group_for_update/{}'.format(
91 plain_http_host_only_stub(), UPDATE_REPO_NAME)
91 plain_http_host_only_stub(), UPDATE_REPO_NAME)
92 }),
92 }),
93 ])
93 ])
94 def test_api_update_repo(self, updates, expected, backend):
94 def test_api_update_repo(self, updates, expected, backend):
95 repo_name = UPDATE_REPO_NAME
95 repo_name = UPDATE_REPO_NAME
96 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
96 repo = fixture.create_repo(repo_name, repo_type=backend.alias)
97 if updates.get('_group'):
97 if updates.get('_group'):
98 fixture.create_repo_group(updates['_group'])
98 fixture.create_repo_group(updates['_group'])
99
99
100 if 'landing_rev' in updates:
100 if 'landing_rev' in updates:
101 default_landing_ref, _lbl = ScmModel.backend_landing_ref(backend.alias)
101 default_landing_ref, _lbl = ScmModel.backend_landing_ref(backend.alias)
102 _type, _name = default_landing_ref.split(':')
102 _type, _name = default_landing_ref.split(':')
103 updates['landing_rev'] = default_landing_ref
103 updates['landing_rev'] = default_landing_ref
104 expected['landing_rev'] = [_type, _name]
104 expected['landing_rev'] = [_type, _name]
105
105
106 expected_api_data = repo.get_api_data(include_secrets=True)
106 expected_api_data = repo.get_api_data(include_secrets=True)
107 if expected is SAME_AS_UPDATES:
107 if expected is SAME_AS_UPDATES:
108 expected_api_data.update(updates)
108 expected_api_data.update(updates)
109 else:
109 else:
110 expected_api_data.update(expected)
110 expected_api_data.update(expected)
111
111
112 id_, params = build_data(
112 id_, params = build_data(
113 self.apikey, 'update_repo', repoid=repo_name, **updates)
113 self.apikey, 'update_repo', repoid=repo_name, **updates)
114
114
115 with mock.patch('rhodecode.model.validation_schema.validators.url_validator'):
115 with mock.patch('rhodecode.model.validation_schema.validators.url_validator'):
116 response = api_call(self.app, params)
116 response = api_call(self.app, params)
117
117
118 if updates.get('repo_name'):
118 if updates.get('repo_name'):
119 repo_name = updates['repo_name']
119 repo_name = updates['repo_name']
120
120
121 try:
121 try:
122 expected = {
122 expected = {
123 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
123 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo_name),
124 'repository': jsonify(expected_api_data)
124 'repository': jsonify(expected_api_data)
125 }
125 }
126 assert_ok(id_, expected, given=response.body)
126 assert_ok(id_, expected, given=response.body)
127 finally:
127 finally:
128 fixture.destroy_repo(repo_name)
128 fixture.destroy_repo(repo_name)
129 if updates.get('_group'):
129 if updates.get('_group'):
130 fixture.destroy_repo_group(updates['_group'])
130 fixture.destroy_repo_group(updates['_group'])
131
131
132 def test_api_update_repo_fork_of_field(self, backend):
132 def test_api_update_repo_fork_of_field(self, backend):
133 master_repo = backend.create_repo()
133 master_repo = backend.create_repo()
134 repo = backend.create_repo()
134 repo = backend.create_repo()
135 updates = {
135 updates = {
136 'fork_of': master_repo.repo_name,
136 'fork_of': master_repo.repo_name,
137 'fork_of_id': master_repo.repo_id
137 'fork_of_id': master_repo.repo_id
138 }
138 }
139 expected_api_data = repo.get_api_data(include_secrets=True)
139 expected_api_data = repo.get_api_data(include_secrets=True)
140 expected_api_data.update(updates)
140 expected_api_data.update(updates)
141
141
142 id_, params = build_data(
142 id_, params = build_data(
143 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
143 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
144 response = api_call(self.app, params)
144 response = api_call(self.app, params)
145 expected = {
145 expected = {
146 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
146 'msg': 'updated repo ID:%s %s' % (repo.repo_id, repo.repo_name),
147 'repository': jsonify(expected_api_data)
147 'repository': jsonify(expected_api_data)
148 }
148 }
149 assert_ok(id_, expected, given=response.body)
149 assert_ok(id_, expected, given=response.body)
150 result = response.json['result']['repository']
150 result = response.json['result']['repository']
151 assert result['fork_of'] == master_repo.repo_name
151 assert result['fork_of'] == master_repo.repo_name
152 assert result['fork_of_id'] == master_repo.repo_id
152 assert result['fork_of_id'] == master_repo.repo_id
153
153
154 def test_api_update_repo_fork_of_not_found(self, backend):
154 def test_api_update_repo_fork_of_not_found(self, backend):
155 master_repo_name = 'fake-parent-repo'
155 master_repo_name = 'fake-parent-repo'
156 repo = backend.create_repo()
156 repo = backend.create_repo()
157 updates = {
157 updates = {
158 'fork_of': master_repo_name
158 'fork_of': master_repo_name
159 }
159 }
160 id_, params = build_data(
160 id_, params = build_data(
161 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
161 self.apikey, 'update_repo', repoid=repo.repo_name, **updates)
162 response = api_call(self.app, params)
162 response = api_call(self.app, params)
163 expected = {
163 expected = {
164 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
164 'repo_fork_of': 'Fork with id `{}` does not exists'.format(
165 master_repo_name)}
165 master_repo_name)}
166 assert_error(id_, expected, given=response.body)
166 assert_error(id_, expected, given=response.body)
167
167
168 def test_api_update_repo_with_repo_group_not_existing(self):
168 def test_api_update_repo_with_repo_group_not_existing(self):
169 repo_name = 'admin_owned'
169 repo_name = 'admin_owned'
170 fake_repo_group = 'test_group_for_update'
170 fake_repo_group = 'test_group_for_update'
171 fixture.create_repo(repo_name)
171 fixture.create_repo(repo_name)
172 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
172 updates = {'repo_name': '{}/{}'.format(fake_repo_group, repo_name)}
173 id_, params = build_data(
173 id_, params = build_data(
174 self.apikey, 'update_repo', repoid=repo_name, **updates)
174 self.apikey, 'update_repo', repoid=repo_name, **updates)
175 response = api_call(self.app, params)
175 response = api_call(self.app, params)
176 try:
176 try:
177 expected = {
177 expected = {
178 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
178 'repo_group': 'Repository group `{}` does not exist'.format(fake_repo_group)
179 }
179 }
180 assert_error(id_, expected, given=response.body)
180 assert_error(id_, expected, given=response.body)
181 finally:
181 finally:
182 fixture.destroy_repo(repo_name)
182 fixture.destroy_repo(repo_name)
183
183
184 def test_api_update_repo_regular_user_not_allowed(self):
184 def test_api_update_repo_regular_user_not_allowed(self):
185 repo_name = 'admin_owned'
185 repo_name = 'admin_owned'
186 fixture.create_repo(repo_name)
186 fixture.create_repo(repo_name)
187 updates = {'active': False}
187 updates = {'active': False}
188 id_, params = build_data(
188 id_, params = build_data(
189 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
189 self.apikey_regular, 'update_repo', repoid=repo_name, **updates)
190 response = api_call(self.app, params)
190 response = api_call(self.app, params)
191 try:
191 try:
192 expected = 'repository `%s` does not exist' % (repo_name,)
192 expected = 'repository `%s` does not exist' % (repo_name,)
193 assert_error(id_, expected, given=response.body)
193 assert_error(id_, expected, given=response.body)
194 finally:
194 finally:
195 fixture.destroy_repo(repo_name)
195 fixture.destroy_repo(repo_name)
196
196
197 @mock.patch.object(RepoModel, 'update', crash)
197 @mock.patch.object(RepoModel, 'update', crash)
198 def test_api_update_repo_exception_occurred(self, backend):
198 def test_api_update_repo_exception_occurred(self, backend):
199 repo_name = UPDATE_REPO_NAME
199 repo_name = UPDATE_REPO_NAME
200 fixture.create_repo(repo_name, repo_type=backend.alias)
200 fixture.create_repo(repo_name, repo_type=backend.alias)
201 id_, params = build_data(
201 id_, params = build_data(
202 self.apikey, 'update_repo', repoid=repo_name,
202 self.apikey, 'update_repo', repoid=repo_name,
203 owner=TEST_USER_ADMIN_LOGIN,)
203 owner=TEST_USER_ADMIN_LOGIN,)
204 response = api_call(self.app, params)
204 response = api_call(self.app, params)
205 try:
205 try:
206 expected = 'failed to update repo `%s`' % (repo_name,)
206 expected = 'failed to update repo `%s`' % (repo_name,)
207 assert_error(id_, expected, given=response.body)
207 assert_error(id_, expected, given=response.body)
208 finally:
208 finally:
209 fixture.destroy_repo(repo_name)
209 fixture.destroy_repo(repo_name)
@@ -1,149 +1,149 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import os
20 import os
21
21
22 import pytest
22 import pytest
23
23
24 from rhodecode.model.repo_group import RepoGroupModel
24 from rhodecode.model.repo_group import RepoGroupModel
25 from rhodecode.model.user import UserModel
25 from rhodecode.model.user import UserModel
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok)
27 build_data, api_call, assert_error, assert_ok)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestApiUpdateRepoGroup(object):
31 class TestApiUpdateRepoGroup(object):
32
32
33 def test_update_group_name(self, user_util):
33 def test_update_group_name(self, user_util):
34 new_group_name = 'new-group'
34 new_group_name = 'new-group'
35 initial_name = self._update(user_util, group_name=new_group_name)
35 initial_name = self._update(user_util, group_name=new_group_name)
36 assert RepoGroupModel()._get_repo_group(initial_name) is None
36 assert RepoGroupModel()._get_repo_group(initial_name) is None
37 new_group = RepoGroupModel()._get_repo_group(new_group_name)
37 new_group = RepoGroupModel()._get_repo_group(new_group_name)
38 assert new_group is not None
38 assert new_group is not None
39 assert new_group.full_path == new_group_name
39 assert new_group.full_path == new_group_name
40
40
41 def test_update_group_name_change_parent(self, user_util):
41 def test_update_group_name_change_parent(self, user_util):
42
42
43 parent_group = user_util.create_repo_group()
43 parent_group = user_util.create_repo_group()
44 parent_group_name = parent_group.name
44 parent_group_name = parent_group.name
45
45
46 expected_group_name = '{}/{}'.format(parent_group_name, 'new-group')
46 expected_group_name = '{}/{}'.format(parent_group_name, 'new-group')
47 initial_name = self._update(user_util, group_name=expected_group_name)
47 initial_name = self._update(user_util, group_name=expected_group_name)
48
48
49 repo_group = RepoGroupModel()._get_repo_group(expected_group_name)
49 repo_group = RepoGroupModel()._get_repo_group(expected_group_name)
50
50
51 assert repo_group is not None
51 assert repo_group is not None
52 assert repo_group.group_name == expected_group_name
52 assert repo_group.group_name == expected_group_name
53 assert repo_group.full_path == expected_group_name
53 assert repo_group.full_path == expected_group_name
54 assert RepoGroupModel()._get_repo_group(initial_name) is None
54 assert RepoGroupModel()._get_repo_group(initial_name) is None
55
55
56 new_path = os.path.join(
56 new_path = os.path.join(
57 RepoGroupModel().repos_path, *repo_group.full_path_splitted)
57 RepoGroupModel().repos_path, *repo_group.full_path_splitted)
58 assert os.path.exists(new_path)
58 assert os.path.exists(new_path)
59
59
60 def test_update_enable_locking(self, user_util):
60 def test_update_enable_locking(self, user_util):
61 initial_name = self._update(user_util, enable_locking=True)
61 initial_name = self._update(user_util, enable_locking=True)
62 repo_group = RepoGroupModel()._get_repo_group(initial_name)
62 repo_group = RepoGroupModel()._get_repo_group(initial_name)
63 assert repo_group.enable_locking is True
63 assert repo_group.enable_locking is True
64
64
65 def test_update_description(self, user_util):
65 def test_update_description(self, user_util):
66 description = 'New description'
66 description = 'New description'
67 initial_name = self._update(user_util, description=description)
67 initial_name = self._update(user_util, description=description)
68 repo_group = RepoGroupModel()._get_repo_group(initial_name)
68 repo_group = RepoGroupModel()._get_repo_group(initial_name)
69 assert repo_group.group_description == description
69 assert repo_group.group_description == description
70
70
71 def test_update_owner(self, user_util):
71 def test_update_owner(self, user_util):
72 owner = self.TEST_USER_LOGIN
72 owner = self.TEST_USER_LOGIN
73 initial_name = self._update(user_util, owner=owner)
73 initial_name = self._update(user_util, owner=owner)
74 repo_group = RepoGroupModel()._get_repo_group(initial_name)
74 repo_group = RepoGroupModel()._get_repo_group(initial_name)
75 assert repo_group.user.username == owner
75 assert repo_group.user.username == owner
76
76
77 def test_update_group_name_conflict_with_existing(self, user_util):
77 def test_update_group_name_conflict_with_existing(self, user_util):
78 group_1 = user_util.create_repo_group()
78 group_1 = user_util.create_repo_group()
79 group_2 = user_util.create_repo_group()
79 group_2 = user_util.create_repo_group()
80 repo_group_name_1 = group_1.group_name
80 repo_group_name_1 = group_1.group_name
81 repo_group_name_2 = group_2.group_name
81 repo_group_name_2 = group_2.group_name
82
82
83 id_, params = build_data(
83 id_, params = build_data(
84 self.apikey, 'update_repo_group', repogroupid=repo_group_name_1,
84 self.apikey, 'update_repo_group', repogroupid=repo_group_name_1,
85 group_name=repo_group_name_2)
85 group_name=repo_group_name_2)
86 response = api_call(self.app, params)
86 response = api_call(self.app, params)
87 expected = {
87 expected = {
88 'unique_repo_group_name':
88 'unique_repo_group_name':
89 'Repository group with name `{}` already exists'.format(
89 'Repository group with name `{}` already exists'.format(
90 repo_group_name_2)}
90 repo_group_name_2)}
91 assert_error(id_, expected, given=response.body)
91 assert_error(id_, expected, given=response.body)
92
92
93 def test_api_update_repo_group_by_regular_user_no_permission(self, user_util):
93 def test_api_update_repo_group_by_regular_user_no_permission(self, user_util):
94 temp_user = user_util.create_user()
94 temp_user = user_util.create_user()
95 temp_user_api_key = temp_user.api_key
95 temp_user_api_key = temp_user.api_key
96 parent_group = user_util.create_repo_group()
96 parent_group = user_util.create_repo_group()
97 repo_group_name = parent_group.group_name
97 repo_group_name = parent_group.group_name
98 id_, params = build_data(
98 id_, params = build_data(
99 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name)
99 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name)
100 response = api_call(self.app, params)
100 response = api_call(self.app, params)
101 expected = 'repository group `%s` does not exist' % (repo_group_name,)
101 expected = 'repository group `%s` does not exist' % (repo_group_name,)
102 assert_error(id_, expected, given=response.body)
102 assert_error(id_, expected, given=response.body)
103
103
104 def test_api_update_repo_group_regular_user_no_root_write_permissions(
104 def test_api_update_repo_group_regular_user_no_root_write_permissions(
105 self, user_util):
105 self, user_util):
106 temp_user = user_util.create_user()
106 temp_user = user_util.create_user()
107 temp_user_api_key = temp_user.api_key
107 temp_user_api_key = temp_user.api_key
108 parent_group = user_util.create_repo_group(owner=temp_user.username)
108 parent_group = user_util.create_repo_group(owner=temp_user.username)
109 repo_group_name = parent_group.group_name
109 repo_group_name = parent_group.group_name
110
110
111 id_, params = build_data(
111 id_, params = build_data(
112 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name,
112 temp_user_api_key, 'update_repo_group', repogroupid=repo_group_name,
113 group_name='at-root-level')
113 group_name='at-root-level')
114 response = api_call(self.app, params)
114 response = api_call(self.app, params)
115 expected = {
115 expected = {
116 'repo_group': 'You do not have the permission to store '
116 'repo_group': 'You do not have the permission to store '
117 'repository groups in the root location.'}
117 'repository groups in the root location.'}
118 assert_error(id_, expected, given=response.body)
118 assert_error(id_, expected, given=response.body)
119
119
120 def _update(self, user_util, **kwargs):
120 def _update(self, user_util, **kwargs):
121 repo_group = user_util.create_repo_group()
121 repo_group = user_util.create_repo_group()
122 initial_name = repo_group.name
122 initial_name = repo_group.name
123 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
123 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
124 user_util.grant_user_permission_to_repo_group(
124 user_util.grant_user_permission_to_repo_group(
125 repo_group, user, 'group.admin')
125 repo_group, user, 'group.admin')
126
126
127 id_, params = build_data(
127 id_, params = build_data(
128 self.apikey, 'update_repo_group', repogroupid=initial_name,
128 self.apikey, 'update_repo_group', repogroupid=initial_name,
129 **kwargs)
129 **kwargs)
130 response = api_call(self.app, params)
130 response = api_call(self.app, params)
131
131
132 repo_group = RepoGroupModel.cls.get(repo_group.group_id)
132 repo_group = RepoGroupModel.cls.get(repo_group.group_id)
133
133
134 expected = {
134 expected = {
135 'msg': 'updated repository group ID:{} {}'.format(
135 'msg': 'updated repository group ID:{} {}'.format(
136 repo_group.group_id, repo_group.group_name),
136 repo_group.group_id, repo_group.group_name),
137 'repo_group': {
137 'repo_group': {
138 'repositories': [],
138 'repositories': [],
139 'group_name': repo_group.group_name,
139 'group_name': repo_group.group_name,
140 'group_description': repo_group.group_description,
140 'group_description': repo_group.group_description,
141 'owner': repo_group.user.username,
141 'owner': repo_group.user.username,
142 'group_id': repo_group.group_id,
142 'group_id': repo_group.group_id,
143 'parent_group': (
143 'parent_group': (
144 repo_group.parent_group.name
144 repo_group.parent_group.name
145 if repo_group.parent_group else None)
145 if repo_group.parent_group else None)
146 }
146 }
147 }
147 }
148 assert_ok(id_, expected, given=response.body)
148 assert_ok(id_, expected, given=response.body)
149 return initial_name
149 return initial_name
@@ -1,120 +1,120 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.db import User
23 from rhodecode.model.db import User
24 from rhodecode.model.user import UserModel
24 from rhodecode.model.user import UserModel
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
25 from rhodecode.tests import TEST_USER_ADMIN_LOGIN
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_ok, assert_error, crash, jsonify)
27 build_data, api_call, assert_ok, assert_error, crash, jsonify)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestUpdateUser(object):
31 class TestUpdateUser(object):
32 @pytest.mark.parametrize("name, expected", [
32 @pytest.mark.parametrize("name, expected", [
33 ('firstname', 'new_username'),
33 ('firstname', 'new_username'),
34 ('lastname', 'new_username'),
34 ('lastname', 'new_username'),
35 ('email', 'new_username'),
35 ('email', 'new_username'),
36 ('admin', True),
36 ('admin', True),
37 ('admin', False),
37 ('admin', False),
38 ('extern_type', 'ldap'),
38 ('extern_type', 'ldap'),
39 ('extern_type', None),
39 ('extern_type', None),
40 ('extern_name', 'test'),
40 ('extern_name', 'test'),
41 ('extern_name', None),
41 ('extern_name', None),
42 ('active', False),
42 ('active', False),
43 ('active', True),
43 ('active', True),
44 ('password', 'newpass'),
44 ('password', 'newpass'),
45 ('description', 'CTO 4 Life')
45 ('description', 'CTO 4 Life')
46 ])
46 ])
47 def test_api_update_user(self, name, expected, user_util):
47 def test_api_update_user(self, name, expected, user_util):
48 usr = user_util.create_user()
48 usr = user_util.create_user()
49
49
50 kw = {name: expected, 'userid': usr.user_id}
50 kw = {name: expected, 'userid': usr.user_id}
51 id_, params = build_data(self.apikey, 'update_user', **kw)
51 id_, params = build_data(self.apikey, 'update_user', **kw)
52 response = api_call(self.app, params)
52 response = api_call(self.app, params)
53
53
54 ret = {
54 ret = {
55 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
55 'msg': 'updated user ID:%s %s' % (usr.user_id, usr.username),
56 'user': jsonify(
56 'user': jsonify(
57 UserModel()
57 UserModel()
58 .get_by_username(usr.username)
58 .get_by_username(usr.username)
59 .get_api_data(include_secrets=True)
59 .get_api_data(include_secrets=True)
60 )
60 )
61 }
61 }
62
62
63 expected = ret
63 expected = ret
64 assert_ok(id_, expected, given=response.body)
64 assert_ok(id_, expected, given=response.body)
65
65
66 def test_api_update_user_no_changed_params(self):
66 def test_api_update_user_no_changed_params(self):
67 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
67 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
68 ret = jsonify(usr.get_api_data(include_secrets=True))
68 ret = jsonify(usr.get_api_data(include_secrets=True))
69 id_, params = build_data(
69 id_, params = build_data(
70 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
70 self.apikey, 'update_user', userid=TEST_USER_ADMIN_LOGIN)
71
71
72 response = api_call(self.app, params)
72 response = api_call(self.app, params)
73 ret = {
73 ret = {
74 'msg': 'updated user ID:%s %s' % (
74 'msg': 'updated user ID:%s %s' % (
75 usr.user_id, TEST_USER_ADMIN_LOGIN),
75 usr.user_id, TEST_USER_ADMIN_LOGIN),
76 'user': ret
76 'user': ret
77 }
77 }
78 expected = ret
78 expected = ret
79 expected['user']['last_activity'] = response.json['result']['user'][
79 expected['user']['last_activity'] = response.json['result']['user'][
80 'last_activity']
80 'last_activity']
81 assert_ok(id_, expected, given=response.body)
81 assert_ok(id_, expected, given=response.body)
82
82
83 def test_api_update_user_by_user_id(self):
83 def test_api_update_user_by_user_id(self):
84 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
84 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
85 ret = jsonify(usr.get_api_data(include_secrets=True))
85 ret = jsonify(usr.get_api_data(include_secrets=True))
86 id_, params = build_data(
86 id_, params = build_data(
87 self.apikey, 'update_user', userid=usr.user_id)
87 self.apikey, 'update_user', userid=usr.user_id)
88
88
89 response = api_call(self.app, params)
89 response = api_call(self.app, params)
90 ret = {
90 ret = {
91 'msg': 'updated user ID:%s %s' % (
91 'msg': 'updated user ID:%s %s' % (
92 usr.user_id, TEST_USER_ADMIN_LOGIN),
92 usr.user_id, TEST_USER_ADMIN_LOGIN),
93 'user': ret
93 'user': ret
94 }
94 }
95 expected = ret
95 expected = ret
96 expected['user']['last_activity'] = response.json['result']['user'][
96 expected['user']['last_activity'] = response.json['result']['user'][
97 'last_activity']
97 'last_activity']
98 assert_ok(id_, expected, given=response.body)
98 assert_ok(id_, expected, given=response.body)
99
99
100 def test_api_update_user_default_user(self):
100 def test_api_update_user_default_user(self):
101 usr = User.get_default_user()
101 usr = User.get_default_user()
102 id_, params = build_data(
102 id_, params = build_data(
103 self.apikey, 'update_user', userid=usr.user_id)
103 self.apikey, 'update_user', userid=usr.user_id)
104
104
105 response = api_call(self.app, params)
105 response = api_call(self.app, params)
106 expected = 'editing default user is forbidden'
106 expected = 'editing default user is forbidden'
107 assert_error(id_, expected, given=response.body)
107 assert_error(id_, expected, given=response.body)
108
108
109 @mock.patch.object(UserModel, 'update_user', crash)
109 @mock.patch.object(UserModel, 'update_user', crash)
110 def test_api_update_user_when_exception_happens(self):
110 def test_api_update_user_when_exception_happens(self):
111 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
111 usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN)
112 ret = jsonify(usr.get_api_data(include_secrets=True))
112 ret = jsonify(usr.get_api_data(include_secrets=True))
113 id_, params = build_data(
113 id_, params = build_data(
114 self.apikey, 'update_user', userid=usr.user_id)
114 self.apikey, 'update_user', userid=usr.user_id)
115
115
116 response = api_call(self.app, params)
116 response = api_call(self.app, params)
117 ret = 'failed to update user `%s`' % (usr.user_id,)
117 ret = 'failed to update user `%s`' % (usr.user_id,)
118
118
119 expected = ret
119 expected = ret
120 assert_error(id_, expected, given=response.body)
120 assert_error(id_, expected, given=response.body)
@@ -1,124 +1,124 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import mock
20 import mock
21 import pytest
21 import pytest
22
22
23 from rhodecode.model.user import UserModel
23 from rhodecode.model.user import UserModel
24 from rhodecode.model.user_group import UserGroupModel
24 from rhodecode.model.user_group import UserGroupModel
25 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
25 from rhodecode.tests import TEST_USER_ADMIN_EMAIL
26 from rhodecode.api.tests.utils import (
26 from rhodecode.api.tests.utils import (
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
27 build_data, api_call, assert_error, assert_ok, crash, jsonify)
28
28
29
29
30 @pytest.mark.usefixtures("testuser_api", "app")
30 @pytest.mark.usefixtures("testuser_api", "app")
31 class TestUpdateUserGroup(object):
31 class TestUpdateUserGroup(object):
32 @pytest.mark.parametrize("changing_attr, updates", [
32 @pytest.mark.parametrize("changing_attr, updates", [
33 ('group_name', {'group_name': 'new_group_name'}),
33 ('group_name', {'group_name': 'new_group_name'}),
34 ('group_name', {'group_name': 'test_group_for_update'}),
34 ('group_name', {'group_name': 'test_group_for_update'}),
35 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
35 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
36 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
36 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
37 ('active', {'active': False}),
37 ('active', {'active': False}),
38 ('active', {'active': True}),
38 ('active', {'active': True}),
39 ('sync', {'sync': False}),
39 ('sync', {'sync': False}),
40 ('sync', {'sync': True})
40 ('sync', {'sync': True})
41 ])
41 ])
42 def test_api_update_user_group(self, changing_attr, updates, user_util):
42 def test_api_update_user_group(self, changing_attr, updates, user_util):
43 user_group = user_util.create_user_group()
43 user_group = user_util.create_user_group()
44 group_name = user_group.users_group_name
44 group_name = user_group.users_group_name
45 expected_api_data = user_group.get_api_data()
45 expected_api_data = user_group.get_api_data()
46 expected_api_data.update(updates)
46 expected_api_data.update(updates)
47
47
48 id_, params = build_data(
48 id_, params = build_data(
49 self.apikey, 'update_user_group', usergroupid=group_name,
49 self.apikey, 'update_user_group', usergroupid=group_name,
50 **updates)
50 **updates)
51 response = api_call(self.app, params)
51 response = api_call(self.app, params)
52
52
53 # special case for sync
53 # special case for sync
54 if changing_attr == 'sync' and updates['sync'] is False:
54 if changing_attr == 'sync' and updates['sync'] is False:
55 expected_api_data['sync'] = None
55 expected_api_data['sync'] = None
56 elif changing_attr == 'sync' and updates['sync'] is True:
56 elif changing_attr == 'sync' and updates['sync'] is True:
57 expected_api_data['sync'] = 'manual_api'
57 expected_api_data['sync'] = 'manual_api'
58
58
59 expected = {
59 expected = {
60 'msg': 'updated user group ID:%s %s' % (
60 'msg': 'updated user group ID:%s %s' % (
61 user_group.users_group_id, user_group.users_group_name),
61 user_group.users_group_id, user_group.users_group_name),
62 'user_group': jsonify(expected_api_data)
62 'user_group': jsonify(expected_api_data)
63 }
63 }
64 assert_ok(id_, expected, given=response.body)
64 assert_ok(id_, expected, given=response.body)
65
65
66 @pytest.mark.parametrize("changing_attr, updates", [
66 @pytest.mark.parametrize("changing_attr, updates", [
67 # TODO: mikhail: decide if we need to test against the commented params
67 # TODO: mikhail: decide if we need to test against the commented params
68 # ('group_name', {'group_name': 'new_group_name'}),
68 # ('group_name', {'group_name': 'new_group_name'}),
69 # ('group_name', {'group_name': 'test_group_for_update'}),
69 # ('group_name', {'group_name': 'test_group_for_update'}),
70 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
70 # ('owner', {'owner': TEST_USER_REGULAR_LOGIN}),
71 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
71 ('owner_email', {'owner_email': TEST_USER_ADMIN_EMAIL}),
72 ('active', {'active': False}),
72 ('active', {'active': False}),
73 ('active', {'active': True}),
73 ('active', {'active': True}),
74 ('sync', {'sync': False}),
74 ('sync', {'sync': False}),
75 ('sync', {'sync': True})
75 ('sync', {'sync': True})
76 ])
76 ])
77 def test_api_update_user_group_regular_user(
77 def test_api_update_user_group_regular_user(
78 self, changing_attr, updates, user_util):
78 self, changing_attr, updates, user_util):
79 user_group = user_util.create_user_group()
79 user_group = user_util.create_user_group()
80 group_name = user_group.users_group_name
80 group_name = user_group.users_group_name
81 expected_api_data = user_group.get_api_data()
81 expected_api_data = user_group.get_api_data()
82 expected_api_data.update(updates)
82 expected_api_data.update(updates)
83
83
84 # grant permission to this user
84 # grant permission to this user
85 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
85 user = UserModel().get_by_username(self.TEST_USER_LOGIN)
86
86
87 user_util.grant_user_permission_to_user_group(
87 user_util.grant_user_permission_to_user_group(
88 user_group, user, 'usergroup.admin')
88 user_group, user, 'usergroup.admin')
89 id_, params = build_data(
89 id_, params = build_data(
90 self.apikey_regular, 'update_user_group',
90 self.apikey_regular, 'update_user_group',
91 usergroupid=group_name, **updates)
91 usergroupid=group_name, **updates)
92 response = api_call(self.app, params)
92 response = api_call(self.app, params)
93 # special case for sync
93 # special case for sync
94 if changing_attr == 'sync' and updates['sync'] is False:
94 if changing_attr == 'sync' and updates['sync'] is False:
95 expected_api_data['sync'] = None
95 expected_api_data['sync'] = None
96 elif changing_attr == 'sync' and updates['sync'] is True:
96 elif changing_attr == 'sync' and updates['sync'] is True:
97 expected_api_data['sync'] = 'manual_api'
97 expected_api_data['sync'] = 'manual_api'
98
98
99 expected = {
99 expected = {
100 'msg': 'updated user group ID:%s %s' % (
100 'msg': 'updated user group ID:%s %s' % (
101 user_group.users_group_id, user_group.users_group_name),
101 user_group.users_group_id, user_group.users_group_name),
102 'user_group': jsonify(expected_api_data)
102 'user_group': jsonify(expected_api_data)
103 }
103 }
104 assert_ok(id_, expected, given=response.body)
104 assert_ok(id_, expected, given=response.body)
105
105
106 def test_api_update_user_group_regular_user_no_permission(self, user_util):
106 def test_api_update_user_group_regular_user_no_permission(self, user_util):
107 user_group = user_util.create_user_group()
107 user_group = user_util.create_user_group()
108 group_name = user_group.users_group_name
108 group_name = user_group.users_group_name
109 id_, params = build_data(
109 id_, params = build_data(
110 self.apikey_regular, 'update_user_group', usergroupid=group_name)
110 self.apikey_regular, 'update_user_group', usergroupid=group_name)
111 response = api_call(self.app, params)
111 response = api_call(self.app, params)
112
112
113 expected = 'user group `%s` does not exist' % (group_name)
113 expected = 'user group `%s` does not exist' % (group_name)
114 assert_error(id_, expected, given=response.body)
114 assert_error(id_, expected, given=response.body)
115
115
116 @mock.patch.object(UserGroupModel, 'update', crash)
116 @mock.patch.object(UserGroupModel, 'update', crash)
117 def test_api_update_user_group_exception_occurred(self, user_util):
117 def test_api_update_user_group_exception_occurred(self, user_util):
118 user_group = user_util.create_user_group()
118 user_group = user_util.create_user_group()
119 group_name = user_group.users_group_name
119 group_name = user_group.users_group_name
120 id_, params = build_data(
120 id_, params = build_data(
121 self.apikey, 'update_user_group', usergroupid=group_name)
121 self.apikey, 'update_user_group', usergroupid=group_name)
122 response = api_call(self.app, params)
122 response = api_call(self.app, params)
123 expected = 'failed to update user group `%s`' % (group_name,)
123 expected = 'failed to update user group `%s`' % (group_name,)
124 assert_error(id_, expected, given=response.body)
124 assert_error(id_, expected, given=response.body)
@@ -1,297 +1,297 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import pytest
21 import pytest
22 from mock import Mock, patch
22 from mock import Mock, patch
23
23
24 from rhodecode.api import utils
24 from rhodecode.api import utils
25 from rhodecode.api import JSONRPCError
25 from rhodecode.api import JSONRPCError
26 from rhodecode.lib.vcs.exceptions import RepositoryError
26 from rhodecode.lib.vcs.exceptions import RepositoryError
27
27
28
28
29 class TestGetCommitOrError(object):
29 class TestGetCommitOrError(object):
30
30
31 def setup_method(self):
31 def setup_method(self):
32 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
32 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
33
33
34 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d', 'branch:name'])
34 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d', 'branch:name'])
35 def test_ref_cannot_be_parsed(self, ref):
35 def test_ref_cannot_be_parsed(self, ref):
36 repo = Mock()
36 repo = Mock()
37 with pytest.raises(JSONRPCError) as excinfo:
37 with pytest.raises(JSONRPCError) as excinfo:
38 utils.get_commit_or_error(ref, repo)
38 utils.get_commit_or_error(ref, repo)
39 expected_message = (
39 expected_message = (
40 'Ref `{ref}` given in a wrong format. Please check the API'
40 'Ref `{ref}` given in a wrong format. Please check the API'
41 ' documentation for more details'.format(ref=ref)
41 ' documentation for more details'.format(ref=ref)
42 )
42 )
43 assert excinfo.value.message == expected_message
43 assert excinfo.value.message == expected_message
44
44
45 def test_success_with_hash_specified(self):
45 def test_success_with_hash_specified(self):
46 repo = Mock()
46 repo = Mock()
47 ref_type = 'branch'
47 ref_type = 'branch'
48 ref = '{}:master:{}'.format(ref_type, self.commit_hash)
48 ref = '{}:master:{}'.format(ref_type, self.commit_hash)
49
49
50 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
50 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
51 result = utils.get_commit_or_error(ref, repo)
51 result = utils.get_commit_or_error(ref, repo)
52 get_commit.assert_called_once_with(
52 get_commit.assert_called_once_with(
53 repo, self.commit_hash)
53 repo, self.commit_hash)
54 assert result == get_commit()
54 assert result == get_commit()
55
55
56 def test_raises_an_error_when_commit_not_found(self):
56 def test_raises_an_error_when_commit_not_found(self):
57 repo = Mock()
57 repo = Mock()
58 ref = 'branch:master:{}'.format(self.commit_hash)
58 ref = 'branch:master:{}'.format(self.commit_hash)
59
59
60 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
60 with patch('rhodecode.api.utils.get_commit_from_ref_name') as get_commit:
61 get_commit.side_effect = RepositoryError('Commit not found')
61 get_commit.side_effect = RepositoryError('Commit not found')
62 with pytest.raises(JSONRPCError) as excinfo:
62 with pytest.raises(JSONRPCError) as excinfo:
63 utils.get_commit_or_error(ref, repo)
63 utils.get_commit_or_error(ref, repo)
64 expected_message = 'Ref `{}` does not exist'.format(ref)
64 expected_message = 'Ref `{}` does not exist'.format(ref)
65 assert excinfo.value.message == expected_message
65 assert excinfo.value.message == expected_message
66
66
67
67
68 class TestResolveRefOrError(object):
68 class TestResolveRefOrError(object):
69
69
70 def setup_method(self):
70 def setup_method(self):
71 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
71 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
72
72
73 def test_success_with_no_hash_specified(self):
73 def test_success_with_no_hash_specified(self):
74 repo = Mock()
74 repo = Mock()
75 ref_type = 'branch'
75 ref_type = 'branch'
76 ref_name = 'master'
76 ref_name = 'master'
77 ref = '{}:{}'.format(ref_type, ref_name)
77 ref = '{}:{}'.format(ref_type, ref_name)
78
78
79 with patch('rhodecode.api.utils._get_ref_hash') \
79 with patch('rhodecode.api.utils._get_ref_hash') \
80 as _get_ref_hash:
80 as _get_ref_hash:
81 _get_ref_hash.return_value = self.commit_hash
81 _get_ref_hash.return_value = self.commit_hash
82 result = utils.resolve_ref_or_error(ref, repo)
82 result = utils.resolve_ref_or_error(ref, repo)
83 _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name)
83 _get_ref_hash.assert_called_once_with(repo, ref_type, ref_name)
84 assert result == '{}:{}'.format(ref, self.commit_hash)
84 assert result == '{}:{}'.format(ref, self.commit_hash)
85
85
86 def test_non_supported_refs(self):
86 def test_non_supported_refs(self):
87 repo = Mock()
87 repo = Mock()
88 ref = 'bookmark:ref'
88 ref = 'bookmark:ref'
89 with pytest.raises(JSONRPCError) as excinfo:
89 with pytest.raises(JSONRPCError) as excinfo:
90 utils.resolve_ref_or_error(ref, repo)
90 utils.resolve_ref_or_error(ref, repo)
91 expected_message = (
91 expected_message = (
92 'The specified value:bookmark:`ref` does not exist, or is not allowed.')
92 'The specified value:bookmark:`ref` does not exist, or is not allowed.')
93 assert excinfo.value.message == expected_message
93 assert excinfo.value.message == expected_message
94
94
95 def test_branch_is_not_found(self):
95 def test_branch_is_not_found(self):
96 repo = Mock()
96 repo = Mock()
97 ref = 'branch:non-existing-one'
97 ref = 'branch:non-existing-one'
98 with patch('rhodecode.api.utils._get_ref_hash')\
98 with patch('rhodecode.api.utils._get_ref_hash')\
99 as _get_ref_hash:
99 as _get_ref_hash:
100 _get_ref_hash.side_effect = KeyError()
100 _get_ref_hash.side_effect = KeyError()
101 with pytest.raises(JSONRPCError) as excinfo:
101 with pytest.raises(JSONRPCError) as excinfo:
102 utils.resolve_ref_or_error(ref, repo)
102 utils.resolve_ref_or_error(ref, repo)
103 expected_message = (
103 expected_message = (
104 'The specified value:branch:`non-existing-one` does not exist, or is not allowed.')
104 'The specified value:branch:`non-existing-one` does not exist, or is not allowed.')
105 assert excinfo.value.message == expected_message
105 assert excinfo.value.message == expected_message
106
106
107 def test_bookmark_is_not_found(self):
107 def test_bookmark_is_not_found(self):
108 repo = Mock()
108 repo = Mock()
109 ref = 'bookmark:non-existing-one'
109 ref = 'bookmark:non-existing-one'
110 with patch('rhodecode.api.utils._get_ref_hash')\
110 with patch('rhodecode.api.utils._get_ref_hash')\
111 as _get_ref_hash:
111 as _get_ref_hash:
112 _get_ref_hash.side_effect = KeyError()
112 _get_ref_hash.side_effect = KeyError()
113 with pytest.raises(JSONRPCError) as excinfo:
113 with pytest.raises(JSONRPCError) as excinfo:
114 utils.resolve_ref_or_error(ref, repo)
114 utils.resolve_ref_or_error(ref, repo)
115 expected_message = (
115 expected_message = (
116 'The specified value:bookmark:`non-existing-one` does not exist, or is not allowed.')
116 'The specified value:bookmark:`non-existing-one` does not exist, or is not allowed.')
117 assert excinfo.value.message == expected_message
117 assert excinfo.value.message == expected_message
118
118
119 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
119 @pytest.mark.parametrize("ref", ['ref', '12345', 'a:b:c:d'])
120 def test_ref_cannot_be_parsed(self, ref):
120 def test_ref_cannot_be_parsed(self, ref):
121 repo = Mock()
121 repo = Mock()
122 with pytest.raises(JSONRPCError) as excinfo:
122 with pytest.raises(JSONRPCError) as excinfo:
123 utils.resolve_ref_or_error(ref, repo)
123 utils.resolve_ref_or_error(ref, repo)
124 expected_message = (
124 expected_message = (
125 'Ref `{ref}` given in a wrong format. Please check the API'
125 'Ref `{ref}` given in a wrong format. Please check the API'
126 ' documentation for more details'.format(ref=ref)
126 ' documentation for more details'.format(ref=ref)
127 )
127 )
128 assert excinfo.value.message == expected_message
128 assert excinfo.value.message == expected_message
129
129
130
130
131 class TestGetRefHash(object):
131 class TestGetRefHash(object):
132
132
133 def setup_method(self):
133 def setup_method(self):
134 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
134 self.commit_hash = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa10'
135 self.bookmark_name = 'test-bookmark'
135 self.bookmark_name = 'test-bookmark'
136
136
137 @pytest.mark.parametrize("alias, branch_name", [
137 @pytest.mark.parametrize("alias, branch_name", [
138 ("git", "master"),
138 ("git", "master"),
139 ("hg", "default")
139 ("hg", "default")
140 ])
140 ])
141 def test_returns_hash_by_branch_name(self, alias, branch_name):
141 def test_returns_hash_by_branch_name(self, alias, branch_name):
142 with patch('rhodecode.model.db.Repository') as repo:
142 with patch('rhodecode.model.db.Repository') as repo:
143 repo.scm_instance().alias = alias
143 repo.scm_instance().alias = alias
144 repo.scm_instance().branches = {branch_name: self.commit_hash}
144 repo.scm_instance().branches = {branch_name: self.commit_hash}
145 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
145 result_hash = utils._get_ref_hash(repo, 'branch', branch_name)
146 assert result_hash == self.commit_hash
146 assert result_hash == self.commit_hash
147
147
148 @pytest.mark.parametrize("alias, branch_name", [
148 @pytest.mark.parametrize("alias, branch_name", [
149 ("git", "master"),
149 ("git", "master"),
150 ("hg", "default")
150 ("hg", "default")
151 ])
151 ])
152 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
152 def test_raises_error_when_branch_is_not_found(self, alias, branch_name):
153 with patch('rhodecode.model.db.Repository') as repo:
153 with patch('rhodecode.model.db.Repository') as repo:
154 repo.scm_instance().alias = alias
154 repo.scm_instance().alias = alias
155 repo.scm_instance().branches = {}
155 repo.scm_instance().branches = {}
156 with pytest.raises(KeyError):
156 with pytest.raises(KeyError):
157 utils._get_ref_hash(repo, 'branch', branch_name)
157 utils._get_ref_hash(repo, 'branch', branch_name)
158
158
159 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
159 def test_returns_hash_when_bookmark_is_specified_for_hg(self):
160 with patch('rhodecode.model.db.Repository') as repo:
160 with patch('rhodecode.model.db.Repository') as repo:
161 repo.scm_instance().alias = 'hg'
161 repo.scm_instance().alias = 'hg'
162 repo.scm_instance().bookmarks = {
162 repo.scm_instance().bookmarks = {
163 self.bookmark_name: self.commit_hash}
163 self.bookmark_name: self.commit_hash}
164 result_hash = utils._get_ref_hash(
164 result_hash = utils._get_ref_hash(
165 repo, 'bookmark', self.bookmark_name)
165 repo, 'bookmark', self.bookmark_name)
166 assert result_hash == self.commit_hash
166 assert result_hash == self.commit_hash
167
167
168 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
168 def test_raises_error_when_bookmark_is_not_found_in_hg_repo(self):
169 with patch('rhodecode.model.db.Repository') as repo:
169 with patch('rhodecode.model.db.Repository') as repo:
170 repo.scm_instance().alias = 'hg'
170 repo.scm_instance().alias = 'hg'
171 repo.scm_instance().bookmarks = {}
171 repo.scm_instance().bookmarks = {}
172 with pytest.raises(KeyError):
172 with pytest.raises(KeyError):
173 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
173 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
174
174
175 def test_raises_error_when_bookmark_is_specified_for_git(self):
175 def test_raises_error_when_bookmark_is_specified_for_git(self):
176 with patch('rhodecode.model.db.Repository') as repo:
176 with patch('rhodecode.model.db.Repository') as repo:
177 repo.scm_instance().alias = 'git'
177 repo.scm_instance().alias = 'git'
178 repo.scm_instance().bookmarks = {
178 repo.scm_instance().bookmarks = {
179 self.bookmark_name: self.commit_hash}
179 self.bookmark_name: self.commit_hash}
180 with pytest.raises(ValueError):
180 with pytest.raises(ValueError):
181 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
181 utils._get_ref_hash(repo, 'bookmark', self.bookmark_name)
182
182
183
183
184 class TestUserByNameOrError(object):
184 class TestUserByNameOrError(object):
185 def test_user_found_by_id(self):
185 def test_user_found_by_id(self):
186 fake_user = Mock(id=123)
186 fake_user = Mock(id=123)
187
187
188 patcher = patch('rhodecode.model.user.UserModel.get_user')
188 patcher = patch('rhodecode.model.user.UserModel.get_user')
189 with patcher as get_user:
189 with patcher as get_user:
190 get_user.return_value = fake_user
190 get_user.return_value = fake_user
191
191
192 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
192 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
193 with patcher as get_by_username:
193 with patcher as get_by_username:
194 result = utils.get_user_or_error(123)
194 result = utils.get_user_or_error(123)
195 assert result == fake_user
195 assert result == fake_user
196
196
197 def test_user_not_found_by_id_as_str(self):
197 def test_user_not_found_by_id_as_str(self):
198 fake_user = Mock(id=123)
198 fake_user = Mock(id=123)
199
199
200 patcher = patch('rhodecode.model.user.UserModel.get_user')
200 patcher = patch('rhodecode.model.user.UserModel.get_user')
201 with patcher as get_user:
201 with patcher as get_user:
202 get_user.return_value = fake_user
202 get_user.return_value = fake_user
203 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
203 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
204 with patcher as get_by_username:
204 with patcher as get_by_username:
205 get_by_username.return_value = None
205 get_by_username.return_value = None
206
206
207 with pytest.raises(JSONRPCError):
207 with pytest.raises(JSONRPCError):
208 utils.get_user_or_error('123')
208 utils.get_user_or_error('123')
209
209
210 def test_user_found_by_name(self):
210 def test_user_found_by_name(self):
211 fake_user = Mock(id=123)
211 fake_user = Mock(id=123)
212
212
213 patcher = patch('rhodecode.model.user.UserModel.get_user')
213 patcher = patch('rhodecode.model.user.UserModel.get_user')
214 with patcher as get_user:
214 with patcher as get_user:
215 get_user.return_value = None
215 get_user.return_value = None
216
216
217 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
217 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
218 with patcher as get_by_username:
218 with patcher as get_by_username:
219 get_by_username.return_value = fake_user
219 get_by_username.return_value = fake_user
220
220
221 result = utils.get_user_or_error('test')
221 result = utils.get_user_or_error('test')
222 assert result == fake_user
222 assert result == fake_user
223
223
224 def test_user_not_found_by_id(self):
224 def test_user_not_found_by_id(self):
225 patcher = patch('rhodecode.model.user.UserModel.get_user')
225 patcher = patch('rhodecode.model.user.UserModel.get_user')
226 with patcher as get_user:
226 with patcher as get_user:
227 get_user.return_value = None
227 get_user.return_value = None
228 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
228 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
229 with patcher as get_by_username:
229 with patcher as get_by_username:
230 get_by_username.return_value = None
230 get_by_username.return_value = None
231
231
232 with pytest.raises(JSONRPCError) as excinfo:
232 with pytest.raises(JSONRPCError) as excinfo:
233 utils.get_user_or_error(123)
233 utils.get_user_or_error(123)
234
234
235 expected_message = 'user `123` does not exist'
235 expected_message = 'user `123` does not exist'
236 assert excinfo.value.message == expected_message
236 assert excinfo.value.message == expected_message
237
237
238 def test_user_not_found_by_name(self):
238 def test_user_not_found_by_name(self):
239 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
239 patcher = patch('rhodecode.model.user.UserModel.get_by_username')
240 with patcher as get_by_username:
240 with patcher as get_by_username:
241 get_by_username.return_value = None
241 get_by_username.return_value = None
242 with pytest.raises(JSONRPCError) as excinfo:
242 with pytest.raises(JSONRPCError) as excinfo:
243 utils.get_user_or_error('test')
243 utils.get_user_or_error('test')
244
244
245 expected_message = 'user `test` does not exist'
245 expected_message = 'user `test` does not exist'
246 assert excinfo.value.message == expected_message
246 assert excinfo.value.message == expected_message
247
247
248
248
249 class TestGetCommitDict(object):
249 class TestGetCommitDict(object):
250 @pytest.mark.parametrize('filename, expected', [
250 @pytest.mark.parametrize('filename, expected', [
251 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
251 (b'sp\xc3\xa4cial', u'sp\xe4cial'),
252 (b'sp\xa4cial', u'sp\ufffdcial'),
252 (b'sp\xa4cial', u'sp\ufffdcial'),
253 ])
253 ])
254 def test_decodes_filenames_to_unicode(self, filename, expected):
254 def test_decodes_filenames_to_unicode(self, filename, expected):
255 result = utils._get_commit_dict(filename=filename, op='A')
255 result = utils._get_commit_dict(filename=filename, op='A')
256 assert result['filename'] == expected
256 assert result['filename'] == expected
257
257
258
258
259 class TestRepoAccess(object):
259 class TestRepoAccess(object):
260 def setup_method(self, method):
260 def setup_method(self, method):
261
261
262 self.admin_perm_patch = patch(
262 self.admin_perm_patch = patch(
263 'rhodecode.api.utils.HasPermissionAnyApi')
263 'rhodecode.api.utils.HasPermissionAnyApi')
264 self.repo_perm_patch = patch(
264 self.repo_perm_patch = patch(
265 'rhodecode.api.utils.HasRepoPermissionAnyApi')
265 'rhodecode.api.utils.HasRepoPermissionAnyApi')
266
266
267 def test_has_superadmin_permission_checks_for_admin(self):
267 def test_has_superadmin_permission_checks_for_admin(self):
268 admin_mock = Mock()
268 admin_mock = Mock()
269 with self.admin_perm_patch as amock:
269 with self.admin_perm_patch as amock:
270 amock.return_value = admin_mock
270 amock.return_value = admin_mock
271 assert utils.has_superadmin_permission('fake_user')
271 assert utils.has_superadmin_permission('fake_user')
272 amock.assert_called_once_with('hg.admin')
272 amock.assert_called_once_with('hg.admin')
273
273
274 admin_mock.assert_called_once_with(user='fake_user')
274 admin_mock.assert_called_once_with(user='fake_user')
275
275
276 def test_has_repo_permissions_checks_for_repo_access(self):
276 def test_has_repo_permissions_checks_for_repo_access(self):
277 repo_mock = Mock()
277 repo_mock = Mock()
278 fake_repo = Mock()
278 fake_repo = Mock()
279 with self.repo_perm_patch as rmock:
279 with self.repo_perm_patch as rmock:
280 rmock.return_value = repo_mock
280 rmock.return_value = repo_mock
281 assert utils.validate_repo_permissions(
281 assert utils.validate_repo_permissions(
282 'fake_user', 'fake_repo_id', fake_repo,
282 'fake_user', 'fake_repo_id', fake_repo,
283 ['perm1', 'perm2'])
283 ['perm1', 'perm2'])
284 rmock.assert_called_once_with(*['perm1', 'perm2'])
284 rmock.assert_called_once_with(*['perm1', 'perm2'])
285
285
286 repo_mock.assert_called_once_with(
286 repo_mock.assert_called_once_with(
287 user='fake_user', repo_name=fake_repo.repo_name)
287 user='fake_user', repo_name=fake_repo.repo_name)
288
288
289 def test_has_repo_permissions_raises_not_found(self):
289 def test_has_repo_permissions_raises_not_found(self):
290 repo_mock = Mock(return_value=False)
290 repo_mock = Mock(return_value=False)
291 fake_repo = Mock()
291 fake_repo = Mock()
292 with self.repo_perm_patch as rmock:
292 with self.repo_perm_patch as rmock:
293 rmock.return_value = repo_mock
293 rmock.return_value = repo_mock
294 with pytest.raises(JSONRPCError) as excinfo:
294 with pytest.raises(JSONRPCError) as excinfo:
295 utils.validate_repo_permissions(
295 utils.validate_repo_permissions(
296 'fake_user', 'fake_repo_id', fake_repo, 'perms')
296 'fake_user', 'fake_repo_id', fake_repo, 'perms')
297 assert 'fake_repo_id' in excinfo
297 assert 'fake_repo_id' in excinfo
@@ -1,123 +1,123 b''
1
1
2 # Copyright (C) 2010-2020 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20
20
21 import random
21 import random
22 import pytest
22 import pytest
23
23
24 from rhodecode.api.utils import get_origin
24 from rhodecode.api.utils import get_origin
25 from rhodecode.lib.ext_json import json
25 from rhodecode.lib.ext_json import json
26
26
27
27
28 def jsonify(obj):
28 def jsonify(obj):
29 return json.loads(json.dumps(obj))
29 return json.loads(json.dumps(obj))
30
30
31
31
32 API_URL = '/_admin/api'
32 API_URL = '/_admin/api'
33
33
34
34
35 def assert_call_ok(id_, given):
35 def assert_call_ok(id_, given):
36 expected = jsonify({
36 expected = jsonify({
37 'id': id_,
37 'id': id_,
38 'error': None,
38 'error': None,
39 'result': None
39 'result': None
40 })
40 })
41 given = json.loads(given)
41 given = json.loads(given)
42
42
43 assert expected['id'] == given['id']
43 assert expected['id'] == given['id']
44 assert expected['error'] == given['error']
44 assert expected['error'] == given['error']
45 return given['result']
45 return given['result']
46
46
47
47
48 def assert_ok(id_, expected, given):
48 def assert_ok(id_, expected, given):
49 given = json.loads(given)
49 given = json.loads(given)
50 if given.get('error'):
50 if given.get('error'):
51 err = given['error']
51 err = given['error']
52 pytest.fail(f"Unexpected ERROR in expected success response: `{err}`")
52 pytest.fail(f"Unexpected ERROR in expected success response: `{err}`")
53
53
54 expected = jsonify({
54 expected = jsonify({
55 'id': id_,
55 'id': id_,
56 'error': None,
56 'error': None,
57 'result': expected
57 'result': expected
58 })
58 })
59
59
60 assert expected == given
60 assert expected == given
61
61
62
62
63 def assert_error(id_, expected, given):
63 def assert_error(id_, expected, given):
64 expected = jsonify({
64 expected = jsonify({
65 'id': id_,
65 'id': id_,
66 'error': expected,
66 'error': expected,
67 'result': None
67 'result': None
68 })
68 })
69 given = json.loads(given)
69 given = json.loads(given)
70 assert expected == given
70 assert expected == given
71
71
72
72
73 def build_data(apikey, method, **kw):
73 def build_data(apikey, method, **kw):
74 """
74 """
75 Builds API data with given random ID
75 Builds API data with given random ID
76 """
76 """
77 random_id = random.randrange(1, 9999)
77 random_id = random.randrange(1, 9999)
78 return random_id, json.dumps({
78 return random_id, json.dumps({
79 "id": random_id,
79 "id": random_id,
80 "api_key": apikey,
80 "api_key": apikey,
81 "method": method,
81 "method": method,
82 "args": kw
82 "args": kw
83 })
83 })
84
84
85
85
86 def api_call(app, params, status=None):
86 def api_call(app, params, status=None):
87 response = app.post(
87 response = app.post(
88 API_URL, content_type='application/json', params=params, status=status,
88 API_URL, content_type='application/json', params=params, status=status,
89 headers=[('Content-Type', 'application/json')])
89 headers=[('Content-Type', 'application/json')])
90 return response
90 return response
91
91
92
92
93 def crash(*args, **kwargs):
93 def crash(*args, **kwargs):
94 raise Exception('Total Crash !')
94 raise Exception('Total Crash !')
95
95
96
96
97 def expected_permissions(object_with_permissions):
97 def expected_permissions(object_with_permissions):
98 """
98 """
99 Returns the expected permissions structure for the given object.
99 Returns the expected permissions structure for the given object.
100
100
101 The object is expected to be a `Repository`, `RepositoryGroup`,
101 The object is expected to be a `Repository`, `RepositoryGroup`,
102 or `UserGroup`. They all implement the same permission handling
102 or `UserGroup`. They all implement the same permission handling
103 API.
103 API.
104 """
104 """
105 permissions = []
105 permissions = []
106 for _user in object_with_permissions.permissions():
106 for _user in object_with_permissions.permissions():
107 user_data = {
107 user_data = {
108 'name': _user.username,
108 'name': _user.username,
109 'permission': _user.permission,
109 'permission': _user.permission,
110 'origin': get_origin(_user),
110 'origin': get_origin(_user),
111 'type': "user",
111 'type': "user",
112 }
112 }
113 permissions.append(user_data)
113 permissions.append(user_data)
114
114
115 for _user_group in object_with_permissions.permission_user_groups():
115 for _user_group in object_with_permissions.permission_user_groups():
116 user_group_data = {
116 user_group_data = {
117 'name': _user_group.users_group_name,
117 'name': _user_group.users_group_name,
118 'permission': _user_group.permission,
118 'permission': _user_group.permission,
119 'origin': get_origin(_user_group),
119 'origin': get_origin(_user_group),
120 'type': "user_group",
120 'type': "user_group",
121 }
121 }
122 permissions.append(user_group_data)
122 permissions.append(user_group_data)
123 return permissions
123 return permissions
@@ -1,458 +1,458 b''
1
1
2
2
3 # Copyright (C) 2014-2020 RhodeCode GmbH
3 # Copyright (C) 2014-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 JSON RPC utils
22 JSON RPC utils
23 """
23 """
24
24
25 import collections
25 import collections
26 import logging
26 import logging
27
27
28 from rhodecode.api.exc import JSONRPCError
28 from rhodecode.api.exc import JSONRPCError
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
30 HasPermissionAnyApi, HasRepoPermissionAnyApi, HasRepoGroupPermissionAnyApi)
31 from rhodecode.lib.str_utils import safe_str
31 from rhodecode.lib.str_utils import safe_str
32 from rhodecode.lib.vcs.exceptions import RepositoryError
32 from rhodecode.lib.vcs.exceptions import RepositoryError
33 from rhodecode.lib.view_utils import get_commit_from_ref_name
33 from rhodecode.lib.view_utils import get_commit_from_ref_name
34 from rhodecode.lib.utils2 import str2bool
34 from rhodecode.lib.utils2 import str2bool
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class OAttr(object):
39 class OAttr(object):
40 """
40 """
41 Special Option that defines other attribute, and can default to them
41 Special Option that defines other attribute, and can default to them
42
42
43 Example::
43 Example::
44
44
45 def test(apiuser, userid=Optional(OAttr('apiuser')):
45 def test(apiuser, userid=Optional(OAttr('apiuser')):
46 user = Optional.extract(userid, evaluate_locals=local())
46 user = Optional.extract(userid, evaluate_locals=local())
47 #if we pass in userid, we get it, else it will default to apiuser
47 #if we pass in userid, we get it, else it will default to apiuser
48 #attribute
48 #attribute
49 """
49 """
50
50
51 def __init__(self, attr_name):
51 def __init__(self, attr_name):
52 self.attr_name = attr_name
52 self.attr_name = attr_name
53
53
54 def __repr__(self):
54 def __repr__(self):
55 return '<OptionalAttr:%s>' % self.attr_name
55 return '<OptionalAttr:%s>' % self.attr_name
56
56
57 def __call__(self):
57 def __call__(self):
58 return self
58 return self
59
59
60
60
61 class Optional(object):
61 class Optional(object):
62 """
62 """
63 Defines an optional parameter::
63 Defines an optional parameter::
64
64
65 param = param.getval() if isinstance(param, Optional) else param
65 param = param.getval() if isinstance(param, Optional) else param
66 param = param() if isinstance(param, Optional) else param
66 param = param() if isinstance(param, Optional) else param
67
67
68 is equivalent of::
68 is equivalent of::
69
69
70 param = Optional.extract(param)
70 param = Optional.extract(param)
71
71
72 """
72 """
73
73
74 def __init__(self, type_):
74 def __init__(self, type_):
75 self.type_ = type_
75 self.type_ = type_
76
76
77 def __repr__(self):
77 def __repr__(self):
78 return '<Optional:%s>' % self.type_.__repr__()
78 return '<Optional:%s>' % self.type_.__repr__()
79
79
80 def __call__(self):
80 def __call__(self):
81 return self.getval()
81 return self.getval()
82
82
83 def getval(self, evaluate_locals=None):
83 def getval(self, evaluate_locals=None):
84 """
84 """
85 returns value from this Optional instance
85 returns value from this Optional instance
86 """
86 """
87 if isinstance(self.type_, OAttr):
87 if isinstance(self.type_, OAttr):
88 param_name = self.type_.attr_name
88 param_name = self.type_.attr_name
89 if evaluate_locals:
89 if evaluate_locals:
90 return evaluate_locals[param_name]
90 return evaluate_locals[param_name]
91 # use params name
91 # use params name
92 return param_name
92 return param_name
93 return self.type_
93 return self.type_
94
94
95 @classmethod
95 @classmethod
96 def extract(cls, val, evaluate_locals=None, binary=None):
96 def extract(cls, val, evaluate_locals=None, binary=None):
97 """
97 """
98 Extracts value from Optional() instance
98 Extracts value from Optional() instance
99
99
100 :param val:
100 :param val:
101 :return: original value if it's not Optional instance else
101 :return: original value if it's not Optional instance else
102 value of instance
102 value of instance
103 """
103 """
104 if isinstance(val, cls):
104 if isinstance(val, cls):
105 val = val.getval(evaluate_locals)
105 val = val.getval(evaluate_locals)
106
106
107 if binary:
107 if binary:
108 val = str2bool(val)
108 val = str2bool(val)
109
109
110 return val
110 return val
111
111
112
112
113 def parse_args(cli_args, key_prefix=''):
113 def parse_args(cli_args, key_prefix=''):
114 from rhodecode.lib.utils2 import (escape_split)
114 from rhodecode.lib.utils2 import (escape_split)
115 kwargs = collections.defaultdict(dict)
115 kwargs = collections.defaultdict(dict)
116 for el in escape_split(cli_args, ','):
116 for el in escape_split(cli_args, ','):
117 kv = escape_split(el, '=', 1)
117 kv = escape_split(el, '=', 1)
118 if len(kv) == 2:
118 if len(kv) == 2:
119 k, v = kv
119 k, v = kv
120 kwargs[key_prefix + k] = v
120 kwargs[key_prefix + k] = v
121 return kwargs
121 return kwargs
122
122
123
123
124 def get_origin(obj):
124 def get_origin(obj):
125 """
125 """
126 Get origin of permission from object.
126 Get origin of permission from object.
127
127
128 :param obj:
128 :param obj:
129 """
129 """
130 origin = 'permission'
130 origin = 'permission'
131
131
132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
132 if getattr(obj, 'owner_row', '') and getattr(obj, 'admin_row', ''):
133 # admin and owner case, maybe we should use dual string ?
133 # admin and owner case, maybe we should use dual string ?
134 origin = 'owner'
134 origin = 'owner'
135 elif getattr(obj, 'owner_row', ''):
135 elif getattr(obj, 'owner_row', ''):
136 origin = 'owner'
136 origin = 'owner'
137 elif getattr(obj, 'admin_row', ''):
137 elif getattr(obj, 'admin_row', ''):
138 origin = 'super-admin'
138 origin = 'super-admin'
139 return origin
139 return origin
140
140
141
141
142 def store_update(updates, attr, name):
142 def store_update(updates, attr, name):
143 """
143 """
144 Stores param in updates dict if it's not instance of Optional
144 Stores param in updates dict if it's not instance of Optional
145 allows easy updates of passed in params
145 allows easy updates of passed in params
146 """
146 """
147 if not isinstance(attr, Optional):
147 if not isinstance(attr, Optional):
148 updates[name] = attr
148 updates[name] = attr
149
149
150
150
151 def has_superadmin_permission(apiuser):
151 def has_superadmin_permission(apiuser):
152 """
152 """
153 Return True if apiuser is admin or return False
153 Return True if apiuser is admin or return False
154
154
155 :param apiuser:
155 :param apiuser:
156 """
156 """
157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
157 if HasPermissionAnyApi('hg.admin')(user=apiuser):
158 return True
158 return True
159 return False
159 return False
160
160
161
161
162 def validate_repo_permissions(apiuser, repoid, repo, perms):
162 def validate_repo_permissions(apiuser, repoid, repo, perms):
163 """
163 """
164 Raise JsonRPCError if apiuser is not authorized or return True
164 Raise JsonRPCError if apiuser is not authorized or return True
165
165
166 :param apiuser:
166 :param apiuser:
167 :param repoid:
167 :param repoid:
168 :param repo:
168 :param repo:
169 :param perms:
169 :param perms:
170 """
170 """
171 if not HasRepoPermissionAnyApi(*perms)(
171 if not HasRepoPermissionAnyApi(*perms)(
172 user=apiuser, repo_name=repo.repo_name):
172 user=apiuser, repo_name=repo.repo_name):
173 raise JSONRPCError('repository `%s` does not exist' % repoid)
173 raise JSONRPCError('repository `%s` does not exist' % repoid)
174
174
175 return True
175 return True
176
176
177
177
178 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
178 def validate_repo_group_permissions(apiuser, repogroupid, repo_group, perms):
179 """
179 """
180 Raise JsonRPCError if apiuser is not authorized or return True
180 Raise JsonRPCError if apiuser is not authorized or return True
181
181
182 :param apiuser:
182 :param apiuser:
183 :param repogroupid: just the id of repository group
183 :param repogroupid: just the id of repository group
184 :param repo_group: instance of repo_group
184 :param repo_group: instance of repo_group
185 :param perms:
185 :param perms:
186 """
186 """
187 if not HasRepoGroupPermissionAnyApi(*perms)(
187 if not HasRepoGroupPermissionAnyApi(*perms)(
188 user=apiuser, group_name=repo_group.group_name):
188 user=apiuser, group_name=repo_group.group_name):
189 raise JSONRPCError(
189 raise JSONRPCError(
190 'repository group `%s` does not exist' % repogroupid)
190 'repository group `%s` does not exist' % repogroupid)
191
191
192 return True
192 return True
193
193
194
194
195 def validate_set_owner_permissions(apiuser, owner):
195 def validate_set_owner_permissions(apiuser, owner):
196 if isinstance(owner, Optional):
196 if isinstance(owner, Optional):
197 owner = get_user_or_error(apiuser.user_id)
197 owner = get_user_or_error(apiuser.user_id)
198 else:
198 else:
199 if has_superadmin_permission(apiuser):
199 if has_superadmin_permission(apiuser):
200 owner = get_user_or_error(owner)
200 owner = get_user_or_error(owner)
201 else:
201 else:
202 # forbid setting owner for non-admins
202 # forbid setting owner for non-admins
203 raise JSONRPCError(
203 raise JSONRPCError(
204 'Only RhodeCode super-admin can specify `owner` param')
204 'Only RhodeCode super-admin can specify `owner` param')
205 return owner
205 return owner
206
206
207
207
208 def get_user_or_error(userid):
208 def get_user_or_error(userid):
209 """
209 """
210 Get user by id or name or return JsonRPCError if not found
210 Get user by id or name or return JsonRPCError if not found
211
211
212 :param userid:
212 :param userid:
213 """
213 """
214 from rhodecode.model.user import UserModel
214 from rhodecode.model.user import UserModel
215 user_model = UserModel()
215 user_model = UserModel()
216
216
217 if isinstance(userid, int):
217 if isinstance(userid, int):
218 try:
218 try:
219 user = user_model.get_user(userid)
219 user = user_model.get_user(userid)
220 except ValueError:
220 except ValueError:
221 user = None
221 user = None
222 else:
222 else:
223 user = user_model.get_by_username(userid)
223 user = user_model.get_by_username(userid)
224
224
225 if user is None:
225 if user is None:
226 raise JSONRPCError(
226 raise JSONRPCError(
227 'user `%s` does not exist' % (userid,))
227 'user `%s` does not exist' % (userid,))
228 return user
228 return user
229
229
230
230
231 def get_repo_or_error(repoid):
231 def get_repo_or_error(repoid):
232 """
232 """
233 Get repo by id or name or return JsonRPCError if not found
233 Get repo by id or name or return JsonRPCError if not found
234
234
235 :param repoid:
235 :param repoid:
236 """
236 """
237 from rhodecode.model.repo import RepoModel
237 from rhodecode.model.repo import RepoModel
238 repo_model = RepoModel()
238 repo_model = RepoModel()
239
239
240 if isinstance(repoid, int):
240 if isinstance(repoid, int):
241 try:
241 try:
242 repo = repo_model.get_repo(repoid)
242 repo = repo_model.get_repo(repoid)
243 except ValueError:
243 except ValueError:
244 repo = None
244 repo = None
245 else:
245 else:
246 repo = repo_model.get_by_repo_name(repoid)
246 repo = repo_model.get_by_repo_name(repoid)
247
247
248 if repo is None:
248 if repo is None:
249 raise JSONRPCError(
249 raise JSONRPCError(
250 'repository `%s` does not exist' % (repoid,))
250 'repository `%s` does not exist' % (repoid,))
251 return repo
251 return repo
252
252
253
253
254 def get_repo_group_or_error(repogroupid):
254 def get_repo_group_or_error(repogroupid):
255 """
255 """
256 Get repo group by id or name or return JsonRPCError if not found
256 Get repo group by id or name or return JsonRPCError if not found
257
257
258 :param repogroupid:
258 :param repogroupid:
259 """
259 """
260 from rhodecode.model.repo_group import RepoGroupModel
260 from rhodecode.model.repo_group import RepoGroupModel
261 repo_group_model = RepoGroupModel()
261 repo_group_model = RepoGroupModel()
262
262
263 if isinstance(repogroupid, int):
263 if isinstance(repogroupid, int):
264 try:
264 try:
265 repo_group = repo_group_model._get_repo_group(repogroupid)
265 repo_group = repo_group_model._get_repo_group(repogroupid)
266 except ValueError:
266 except ValueError:
267 repo_group = None
267 repo_group = None
268 else:
268 else:
269 repo_group = repo_group_model.get_by_group_name(repogroupid)
269 repo_group = repo_group_model.get_by_group_name(repogroupid)
270
270
271 if repo_group is None:
271 if repo_group is None:
272 raise JSONRPCError(
272 raise JSONRPCError(
273 'repository group `%s` does not exist' % (repogroupid,))
273 'repository group `%s` does not exist' % (repogroupid,))
274 return repo_group
274 return repo_group
275
275
276
276
277 def get_user_group_or_error(usergroupid):
277 def get_user_group_or_error(usergroupid):
278 """
278 """
279 Get user group by id or name or return JsonRPCError if not found
279 Get user group by id or name or return JsonRPCError if not found
280
280
281 :param usergroupid:
281 :param usergroupid:
282 """
282 """
283 from rhodecode.model.user_group import UserGroupModel
283 from rhodecode.model.user_group import UserGroupModel
284 user_group_model = UserGroupModel()
284 user_group_model = UserGroupModel()
285
285
286 if isinstance(usergroupid, int):
286 if isinstance(usergroupid, int):
287 try:
287 try:
288 user_group = user_group_model.get_group(usergroupid)
288 user_group = user_group_model.get_group(usergroupid)
289 except ValueError:
289 except ValueError:
290 user_group = None
290 user_group = None
291 else:
291 else:
292 user_group = user_group_model.get_by_name(usergroupid)
292 user_group = user_group_model.get_by_name(usergroupid)
293
293
294 if user_group is None:
294 if user_group is None:
295 raise JSONRPCError(
295 raise JSONRPCError(
296 'user group `%s` does not exist' % (usergroupid,))
296 'user group `%s` does not exist' % (usergroupid,))
297 return user_group
297 return user_group
298
298
299
299
300 def get_perm_or_error(permid, prefix=None):
300 def get_perm_or_error(permid, prefix=None):
301 """
301 """
302 Get permission by id or name or return JsonRPCError if not found
302 Get permission by id or name or return JsonRPCError if not found
303
303
304 :param permid:
304 :param permid:
305 """
305 """
306 from rhodecode.model.permission import PermissionModel
306 from rhodecode.model.permission import PermissionModel
307
307
308 perm = PermissionModel.cls.get_by_key(permid)
308 perm = PermissionModel.cls.get_by_key(permid)
309 if perm is None:
309 if perm is None:
310 msg = 'permission `{}` does not exist.'.format(permid)
310 msg = 'permission `{}` does not exist.'.format(permid)
311 if prefix:
311 if prefix:
312 msg += ' Permission should start with prefix: `{}`'.format(prefix)
312 msg += ' Permission should start with prefix: `{}`'.format(prefix)
313 raise JSONRPCError(msg)
313 raise JSONRPCError(msg)
314
314
315 if prefix:
315 if prefix:
316 if not perm.permission_name.startswith(prefix):
316 if not perm.permission_name.startswith(prefix):
317 raise JSONRPCError('permission `%s` is invalid, '
317 raise JSONRPCError('permission `%s` is invalid, '
318 'should start with %s' % (permid, prefix))
318 'should start with %s' % (permid, prefix))
319 return perm
319 return perm
320
320
321
321
322 def get_gist_or_error(gistid):
322 def get_gist_or_error(gistid):
323 """
323 """
324 Get gist by id or gist_access_id or return JsonRPCError if not found
324 Get gist by id or gist_access_id or return JsonRPCError if not found
325
325
326 :param gistid:
326 :param gistid:
327 """
327 """
328 from rhodecode.model.gist import GistModel
328 from rhodecode.model.gist import GistModel
329
329
330 gist = GistModel.cls.get_by_access_id(gistid)
330 gist = GistModel.cls.get_by_access_id(gistid)
331 if gist is None:
331 if gist is None:
332 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
332 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
333 return gist
333 return gist
334
334
335
335
336 def get_pull_request_or_error(pullrequestid):
336 def get_pull_request_or_error(pullrequestid):
337 """
337 """
338 Get pull request by id or return JsonRPCError if not found
338 Get pull request by id or return JsonRPCError if not found
339
339
340 :param pullrequestid:
340 :param pullrequestid:
341 """
341 """
342 from rhodecode.model.pull_request import PullRequestModel
342 from rhodecode.model.pull_request import PullRequestModel
343
343
344 try:
344 try:
345 pull_request = PullRequestModel().get(int(pullrequestid))
345 pull_request = PullRequestModel().get(int(pullrequestid))
346 except ValueError:
346 except ValueError:
347 raise JSONRPCError('pullrequestid must be an integer')
347 raise JSONRPCError('pullrequestid must be an integer')
348 if not pull_request:
348 if not pull_request:
349 raise JSONRPCError('pull request `%s` does not exist' % (
349 raise JSONRPCError('pull request `%s` does not exist' % (
350 pullrequestid,))
350 pullrequestid,))
351 return pull_request
351 return pull_request
352
352
353
353
354 def build_commit_data(rhodecode_vcs_repo, commit, detail_level):
354 def build_commit_data(rhodecode_vcs_repo, commit, detail_level):
355 commit2 = commit
355 commit2 = commit
356 commit1 = commit.first_parent
356 commit1 = commit.first_parent
357
357
358 parsed_diff = []
358 parsed_diff = []
359 if detail_level == 'extended':
359 if detail_level == 'extended':
360 for f_path in commit.added_paths:
360 for f_path in commit.added_paths:
361 parsed_diff.append(_get_commit_dict(filename=f_path, op='A'))
361 parsed_diff.append(_get_commit_dict(filename=f_path, op='A'))
362 for f_path in commit.changed_paths:
362 for f_path in commit.changed_paths:
363 parsed_diff.append(_get_commit_dict(filename=f_path, op='M'))
363 parsed_diff.append(_get_commit_dict(filename=f_path, op='M'))
364 for f_path in commit.removed_paths:
364 for f_path in commit.removed_paths:
365 parsed_diff.append(_get_commit_dict(filename=f_path, op='D'))
365 parsed_diff.append(_get_commit_dict(filename=f_path, op='D'))
366
366
367 elif detail_level == 'full':
367 elif detail_level == 'full':
368 from rhodecode.lib import diffs
368 from rhodecode.lib import diffs
369
369
370 _diff = rhodecode_vcs_repo.get_diff(commit1, commit2,)
370 _diff = rhodecode_vcs_repo.get_diff(commit1, commit2,)
371 diff_processor = diffs.DiffProcessor(_diff, diff_format='newdiff', show_full_diff=True)
371 diff_processor = diffs.DiffProcessor(_diff, diff_format='newdiff', show_full_diff=True)
372
372
373 for dp in diff_processor.prepare():
373 for dp in diff_processor.prepare():
374 del dp['stats']['ops']
374 del dp['stats']['ops']
375 _stats = dp['stats']
375 _stats = dp['stats']
376 parsed_diff.append(_get_commit_dict(
376 parsed_diff.append(_get_commit_dict(
377 filename=dp['filename'], op=dp['operation'],
377 filename=dp['filename'], op=dp['operation'],
378 new_revision=dp['new_revision'],
378 new_revision=dp['new_revision'],
379 old_revision=dp['old_revision'],
379 old_revision=dp['old_revision'],
380 raw_diff=dp['raw_diff'], stats=_stats))
380 raw_diff=dp['raw_diff'], stats=_stats))
381
381
382 return parsed_diff
382 return parsed_diff
383
383
384
384
385 def get_commit_or_error(ref, repo):
385 def get_commit_or_error(ref, repo):
386 try:
386 try:
387 ref_type, _, ref_hash = ref.split(':')
387 ref_type, _, ref_hash = ref.split(':')
388 except ValueError:
388 except ValueError:
389 raise JSONRPCError(
389 raise JSONRPCError(
390 'Ref `{ref}` given in a wrong format. Please check the API'
390 'Ref `{ref}` given in a wrong format. Please check the API'
391 ' documentation for more details'.format(ref=ref))
391 ' documentation for more details'.format(ref=ref))
392 try:
392 try:
393 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
393 # TODO: dan: refactor this to use repo.scm_instance().get_commit()
394 # once get_commit supports ref_types
394 # once get_commit supports ref_types
395 return get_commit_from_ref_name(repo, ref_hash)
395 return get_commit_from_ref_name(repo, ref_hash)
396 except RepositoryError:
396 except RepositoryError:
397 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
397 raise JSONRPCError('Ref `{ref}` does not exist'.format(ref=ref))
398
398
399
399
400 def _get_ref_hash(repo, type_, name):
400 def _get_ref_hash(repo, type_, name):
401 vcs_repo = repo.scm_instance()
401 vcs_repo = repo.scm_instance()
402 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
402 if type_ in ['branch'] and vcs_repo.alias in ('hg', 'git'):
403 return vcs_repo.branches[name]
403 return vcs_repo.branches[name]
404 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
404 elif type_ in ['bookmark', 'book'] and vcs_repo.alias == 'hg':
405 return vcs_repo.bookmarks[name]
405 return vcs_repo.bookmarks[name]
406 else:
406 else:
407 raise ValueError()
407 raise ValueError()
408
408
409
409
410 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
410 def resolve_ref_or_error(ref, repo, allowed_ref_types=None):
411 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
411 allowed_ref_types = allowed_ref_types or ['bookmark', 'book', 'tag', 'branch']
412
412
413 def _parse_ref(type_, name, hash_=None):
413 def _parse_ref(type_, name, hash_=None):
414 return type_, name, hash_
414 return type_, name, hash_
415
415
416 try:
416 try:
417 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
417 ref_type, ref_name, ref_hash = _parse_ref(*ref.split(':'))
418 except TypeError:
418 except TypeError:
419 raise JSONRPCError(
419 raise JSONRPCError(
420 'Ref `{ref}` given in a wrong format. Please check the API'
420 'Ref `{ref}` given in a wrong format. Please check the API'
421 ' documentation for more details'.format(ref=ref))
421 ' documentation for more details'.format(ref=ref))
422
422
423 if ref_type not in allowed_ref_types:
423 if ref_type not in allowed_ref_types:
424 raise JSONRPCError(
424 raise JSONRPCError(
425 'Ref `{ref}` type is not allowed. '
425 'Ref `{ref}` type is not allowed. '
426 'Only:{allowed_refs} are possible.'.format(
426 'Only:{allowed_refs} are possible.'.format(
427 ref=ref, allowed_refs=allowed_ref_types))
427 ref=ref, allowed_refs=allowed_ref_types))
428
428
429 try:
429 try:
430 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
430 ref_hash = ref_hash or _get_ref_hash(repo, ref_type, ref_name)
431 except (KeyError, ValueError):
431 except (KeyError, ValueError):
432 raise JSONRPCError(
432 raise JSONRPCError(
433 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
433 'The specified value:{type}:`{name}` does not exist, or is not allowed.'.format(
434 type=ref_type, name=ref_name))
434 type=ref_type, name=ref_name))
435
435
436 return ':'.join([ref_type, ref_name, ref_hash])
436 return ':'.join([ref_type, ref_name, ref_hash])
437
437
438
438
439 def _get_commit_dict(
439 def _get_commit_dict(
440 filename, op, new_revision=None, old_revision=None,
440 filename, op, new_revision=None, old_revision=None,
441 raw_diff=None, stats=None):
441 raw_diff=None, stats=None):
442 if stats is None:
442 if stats is None:
443 stats = {
443 stats = {
444 "added": None,
444 "added": None,
445 "binary": None,
445 "binary": None,
446 "deleted": None
446 "deleted": None
447 }
447 }
448 return {
448 return {
449 "filename": safe_str(filename),
449 "filename": safe_str(filename),
450 "op": op,
450 "op": op,
451
451
452 # extra details
452 # extra details
453 "new_revision": new_revision,
453 "new_revision": new_revision,
454 "old_revision": old_revision,
454 "old_revision": old_revision,
455
455
456 "raw_diff": raw_diff,
456 "raw_diff": raw_diff,
457 "stats": stats
457 "stats": stats
458 }
458 }
@@ -1,19 +1,19 b''
1
1
2
2
3 # Copyright (C) 2015-2020 RhodeCode GmbH
3 # Copyright (C) 2015-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -1,102 +1,102 b''
1
1
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 NOTE:
23 NOTE:
24 Place for deprecated APIs here, if a call needs to be deprecated, please
24 Place for deprecated APIs here, if a call needs to be deprecated, please
25 put it here, and point to a new version
25 put it here, and point to a new version
26 """
26 """
27 import logging
27 import logging
28
28
29 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method
29 from rhodecode.api import jsonrpc_method, jsonrpc_deprecated_method
30 from rhodecode.api.utils import Optional, OAttr
30 from rhodecode.api.utils import Optional, OAttr
31
31
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 # permission check inside
36 # permission check inside
37 @jsonrpc_method()
37 @jsonrpc_method()
38 @jsonrpc_deprecated_method(
38 @jsonrpc_deprecated_method(
39 use_method='comment_commit', deprecated_at_version='3.4.0')
39 use_method='comment_commit', deprecated_at_version='3.4.0')
40 def changeset_comment(request, apiuser, repoid, revision, message,
40 def changeset_comment(request, apiuser, repoid, revision, message,
41 userid=Optional(OAttr('apiuser')),
41 userid=Optional(OAttr('apiuser')),
42 status=Optional(None)):
42 status=Optional(None)):
43 """
43 """
44 Set a changeset comment, and optionally change the status of the
44 Set a changeset comment, and optionally change the status of the
45 changeset.
45 changeset.
46
46
47 This command can only be run using an |authtoken| with admin
47 This command can only be run using an |authtoken| with admin
48 permissions on the |repo|.
48 permissions on the |repo|.
49
49
50 :param apiuser: This is filled automatically from the |authtoken|.
50 :param apiuser: This is filled automatically from the |authtoken|.
51 :type apiuser: AuthUser
51 :type apiuser: AuthUser
52 :param repoid: Set the repository name or repository ID.
52 :param repoid: Set the repository name or repository ID.
53 :type repoid: str or int
53 :type repoid: str or int
54 :param revision: Specify the revision for which to set a comment.
54 :param revision: Specify the revision for which to set a comment.
55 :type revision: str
55 :type revision: str
56 :param message: The comment text.
56 :param message: The comment text.
57 :type message: str
57 :type message: str
58 :param userid: Set the user name of the comment creator.
58 :param userid: Set the user name of the comment creator.
59 :type userid: Optional(str or int)
59 :type userid: Optional(str or int)
60 :param status: Set the comment status. The following are valid options:
60 :param status: Set the comment status. The following are valid options:
61 * not_reviewed
61 * not_reviewed
62 * approved
62 * approved
63 * rejected
63 * rejected
64 * under_review
64 * under_review
65 :type status: str
65 :type status: str
66
66
67 Example error output:
67 Example error output:
68
68
69 .. code-block:: javascript
69 .. code-block:: javascript
70
70
71 {
71 {
72 "id" : <id_given_in_input>,
72 "id" : <id_given_in_input>,
73 "result" : {
73 "result" : {
74 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
74 "msg": "Commented on commit `<revision>` for repository `<repoid>`",
75 "status_change": null or <status>,
75 "status_change": null or <status>,
76 "success": true
76 "success": true
77 },
77 },
78 "error" : null
78 "error" : null
79 }
79 }
80
80
81 """
81 """
82 from .repo_api import comment_commit
82 from .repo_api import comment_commit
83
83
84 return comment_commit(request=request,
84 return comment_commit(request=request,
85 apiuser=apiuser, repoid=repoid, commit_id=revision,
85 apiuser=apiuser, repoid=repoid, commit_id=revision,
86 message=message, userid=userid, status=status)
86 message=message, userid=userid, status=status)
87
87
88
88
89 @jsonrpc_method()
89 @jsonrpc_method()
90 @jsonrpc_deprecated_method(
90 @jsonrpc_deprecated_method(
91 use_method='get_ip', deprecated_at_version='4.0.0')
91 use_method='get_ip', deprecated_at_version='4.0.0')
92 def show_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
92 def show_ip(request, apiuser, userid=Optional(OAttr('apiuser'))):
93 from .server_api import get_ip
93 from .server_api import get_ip
94 return get_ip(request=request, apiuser=apiuser, userid=userid)
94 return get_ip(request=request, apiuser=apiuser, userid=userid)
95
95
96
96
97 @jsonrpc_method()
97 @jsonrpc_method()
98 @jsonrpc_deprecated_method(
98 @jsonrpc_deprecated_method(
99 use_method='get_user_locks', deprecated_at_version='4.0.0')
99 use_method='get_user_locks', deprecated_at_version='4.0.0')
100 def get_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
100 def get_locks(request, apiuser, userid=Optional(OAttr('apiuser'))):
101 from .user_api import get_user_locks
101 from .user_api import get_user_locks
102 return get_user_locks(request=request, apiuser=apiuser, userid=userid) No newline at end of file
102 return get_user_locks(request=request, apiuser=apiuser, userid=userid)
@@ -1,257 +1,257 b''
1
1
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 import time
23 import time
24
24
25 from rhodecode.api import jsonrpc_method, JSONRPCError
25 from rhodecode.api import jsonrpc_method, JSONRPCError
26 from rhodecode.api.exc import JSONRPCValidationError
26 from rhodecode.api.exc import JSONRPCValidationError
27 from rhodecode.api.utils import (
27 from rhodecode.api.utils import (
28 Optional, OAttr, get_gist_or_error, get_user_or_error,
28 Optional, OAttr, get_gist_or_error, get_user_or_error,
29 has_superadmin_permission)
29 has_superadmin_permission)
30 from rhodecode.model.db import Session, or_
30 from rhodecode.model.db import Session, or_
31 from rhodecode.model.gist import Gist, GistModel
31 from rhodecode.model.gist import Gist, GistModel
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 @jsonrpc_method()
36 @jsonrpc_method()
37 def get_gist(request, apiuser, gistid, content=Optional(False)):
37 def get_gist(request, apiuser, gistid, content=Optional(False)):
38 """
38 """
39 Get the specified gist, based on the gist ID.
39 Get the specified gist, based on the gist ID.
40
40
41 :param apiuser: This is filled automatically from the |authtoken|.
41 :param apiuser: This is filled automatically from the |authtoken|.
42 :type apiuser: AuthUser
42 :type apiuser: AuthUser
43 :param gistid: Set the id of the private or public gist
43 :param gistid: Set the id of the private or public gist
44 :type gistid: str
44 :type gistid: str
45 :param content: Return the gist content. Default is false.
45 :param content: Return the gist content. Default is false.
46 :type content: Optional(bool)
46 :type content: Optional(bool)
47 """
47 """
48
48
49 gist = get_gist_or_error(gistid)
49 gist = get_gist_or_error(gistid)
50 content = Optional.extract(content)
50 content = Optional.extract(content)
51
51
52 if not has_superadmin_permission(apiuser):
52 if not has_superadmin_permission(apiuser):
53 if gist.gist_owner != apiuser.user_id:
53 if gist.gist_owner != apiuser.user_id:
54 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
54 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
55 data = gist.get_api_data()
55 data = gist.get_api_data()
56
56
57 if content:
57 if content:
58 from rhodecode.model.gist import GistModel
58 from rhodecode.model.gist import GistModel
59 rev, gist_files = GistModel().get_gist_files(gistid)
59 rev, gist_files = GistModel().get_gist_files(gistid)
60 data['content'] = dict([(x.path, x.str_content) for x in gist_files])
60 data['content'] = dict([(x.path, x.str_content) for x in gist_files])
61 return data
61 return data
62
62
63
63
64 @jsonrpc_method()
64 @jsonrpc_method()
65 def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))):
65 def get_gists(request, apiuser, userid=Optional(OAttr('apiuser'))):
66 """
66 """
67 Get all gists for given user. If userid is empty returned gists
67 Get all gists for given user. If userid is empty returned gists
68 are for user who called the api
68 are for user who called the api
69
69
70 :param apiuser: This is filled automatically from the |authtoken|.
70 :param apiuser: This is filled automatically from the |authtoken|.
71 :type apiuser: AuthUser
71 :type apiuser: AuthUser
72 :param userid: user to get gists for
72 :param userid: user to get gists for
73 :type userid: Optional(str or int)
73 :type userid: Optional(str or int)
74 """
74 """
75
75
76 if not has_superadmin_permission(apiuser):
76 if not has_superadmin_permission(apiuser):
77 # make sure normal user does not pass someone else userid,
77 # make sure normal user does not pass someone else userid,
78 # he is not allowed to do that
78 # he is not allowed to do that
79 if not isinstance(userid, Optional) and userid != apiuser.user_id:
79 if not isinstance(userid, Optional) and userid != apiuser.user_id:
80 raise JSONRPCError(
80 raise JSONRPCError(
81 'userid is not the same as your user'
81 'userid is not the same as your user'
82 )
82 )
83
83
84 if isinstance(userid, Optional):
84 if isinstance(userid, Optional):
85 user_id = apiuser.user_id
85 user_id = apiuser.user_id
86 else:
86 else:
87 user_id = get_user_or_error(userid).user_id
87 user_id = get_user_or_error(userid).user_id
88
88
89 gists = []
89 gists = []
90 _gists = Gist().query() \
90 _gists = Gist().query() \
91 .filter(or_(
91 .filter(or_(
92 Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \
92 Gist.gist_expires == -1, Gist.gist_expires >= time.time())) \
93 .filter(Gist.gist_owner == user_id) \
93 .filter(Gist.gist_owner == user_id) \
94 .order_by(Gist.created_on.desc())
94 .order_by(Gist.created_on.desc())
95 for gist in _gists:
95 for gist in _gists:
96 gists.append(gist.get_api_data())
96 gists.append(gist.get_api_data())
97 return gists
97 return gists
98
98
99
99
100 @jsonrpc_method()
100 @jsonrpc_method()
101 def create_gist(
101 def create_gist(
102 request, apiuser, files, gistid=Optional(None),
102 request, apiuser, files, gistid=Optional(None),
103 owner=Optional(OAttr('apiuser')),
103 owner=Optional(OAttr('apiuser')),
104 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
104 gist_type=Optional(Gist.GIST_PUBLIC), lifetime=Optional(-1),
105 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
105 acl_level=Optional(Gist.ACL_LEVEL_PUBLIC),
106 description=Optional('')):
106 description=Optional('')):
107 """
107 """
108 Creates a new Gist.
108 Creates a new Gist.
109
109
110 :param apiuser: This is filled automatically from the |authtoken|.
110 :param apiuser: This is filled automatically from the |authtoken|.
111 :type apiuser: AuthUser
111 :type apiuser: AuthUser
112 :param files: files to be added to the gist. The data structure has
112 :param files: files to be added to the gist. The data structure has
113 to match the following example::
113 to match the following example::
114
114
115 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
115 {'filename1': {'content':'...'}, 'filename2': {'content':'...'}}
116
116
117 :type files: dict
117 :type files: dict
118 :param gistid: Set a custom id for the gist
118 :param gistid: Set a custom id for the gist
119 :type gistid: Optional(str)
119 :type gistid: Optional(str)
120 :param owner: Set the gist owner, defaults to api method caller
120 :param owner: Set the gist owner, defaults to api method caller
121 :type owner: Optional(str or int)
121 :type owner: Optional(str or int)
122 :param gist_type: type of gist ``public`` or ``private``
122 :param gist_type: type of gist ``public`` or ``private``
123 :type gist_type: Optional(str)
123 :type gist_type: Optional(str)
124 :param lifetime: time in minutes of gist lifetime
124 :param lifetime: time in minutes of gist lifetime
125 :type lifetime: Optional(int)
125 :type lifetime: Optional(int)
126 :param acl_level: acl level for this gist, can be
126 :param acl_level: acl level for this gist, can be
127 ``acl_public`` or ``acl_private`` If the value is set to
127 ``acl_public`` or ``acl_private`` If the value is set to
128 ``acl_private`` only logged in users are able to access this gist.
128 ``acl_private`` only logged in users are able to access this gist.
129 If not set it defaults to ``acl_public``.
129 If not set it defaults to ``acl_public``.
130 :type acl_level: Optional(str)
130 :type acl_level: Optional(str)
131 :param description: gist description
131 :param description: gist description
132 :type description: Optional(str)
132 :type description: Optional(str)
133
133
134 Example output:
134 Example output:
135
135
136 .. code-block:: bash
136 .. code-block:: bash
137
137
138 id : <id_given_in_input>
138 id : <id_given_in_input>
139 result : {
139 result : {
140 "msg": "created new gist",
140 "msg": "created new gist",
141 "gist": {}
141 "gist": {}
142 }
142 }
143 error : null
143 error : null
144
144
145 Example error output:
145 Example error output:
146
146
147 .. code-block:: bash
147 .. code-block:: bash
148
148
149 id : <id_given_in_input>
149 id : <id_given_in_input>
150 result : null
150 result : null
151 error : {
151 error : {
152 "failed to create gist"
152 "failed to create gist"
153 }
153 }
154
154
155 """
155 """
156 from rhodecode.model import validation_schema
156 from rhodecode.model import validation_schema
157 from rhodecode.model.validation_schema.schemas import gist_schema
157 from rhodecode.model.validation_schema.schemas import gist_schema
158
158
159 if isinstance(owner, Optional):
159 if isinstance(owner, Optional):
160 owner = apiuser.user_id
160 owner = apiuser.user_id
161
161
162 owner = get_user_or_error(owner)
162 owner = get_user_or_error(owner)
163
163
164 lifetime = Optional.extract(lifetime)
164 lifetime = Optional.extract(lifetime)
165 schema = gist_schema.GistSchema().bind(
165 schema = gist_schema.GistSchema().bind(
166 # bind the given values if it's allowed, however the deferred
166 # bind the given values if it's allowed, however the deferred
167 # validator will still validate it according to other rules
167 # validator will still validate it according to other rules
168 lifetime_options=[lifetime])
168 lifetime_options=[lifetime])
169
169
170 try:
170 try:
171 nodes = gist_schema.nodes_to_sequence(
171 nodes = gist_schema.nodes_to_sequence(
172 files, colander_node=schema.get('nodes'))
172 files, colander_node=schema.get('nodes'))
173
173
174 schema_data = schema.deserialize(dict(
174 schema_data = schema.deserialize(dict(
175 gistid=Optional.extract(gistid),
175 gistid=Optional.extract(gistid),
176 description=Optional.extract(description),
176 description=Optional.extract(description),
177 gist_type=Optional.extract(gist_type),
177 gist_type=Optional.extract(gist_type),
178 lifetime=lifetime,
178 lifetime=lifetime,
179 gist_acl_level=Optional.extract(acl_level),
179 gist_acl_level=Optional.extract(acl_level),
180 nodes=nodes
180 nodes=nodes
181 ))
181 ))
182
182
183 # convert to safer format with just KEYs so we sure no duplicates
183 # convert to safer format with just KEYs so we sure no duplicates
184 schema_data['nodes'] = gist_schema.sequence_to_nodes(
184 schema_data['nodes'] = gist_schema.sequence_to_nodes(
185 schema_data['nodes'], colander_node=schema.get('nodes'))
185 schema_data['nodes'], colander_node=schema.get('nodes'))
186
186
187 except validation_schema.Invalid as err:
187 except validation_schema.Invalid as err:
188 raise JSONRPCValidationError(colander_exc=err)
188 raise JSONRPCValidationError(colander_exc=err)
189
189
190 try:
190 try:
191 gist = GistModel().create(
191 gist = GistModel().create(
192 owner=owner,
192 owner=owner,
193 gist_id=schema_data['gistid'],
193 gist_id=schema_data['gistid'],
194 description=schema_data['description'],
194 description=schema_data['description'],
195 gist_mapping=schema_data['nodes'],
195 gist_mapping=schema_data['nodes'],
196 gist_type=schema_data['gist_type'],
196 gist_type=schema_data['gist_type'],
197 lifetime=schema_data['lifetime'],
197 lifetime=schema_data['lifetime'],
198 gist_acl_level=schema_data['gist_acl_level'])
198 gist_acl_level=schema_data['gist_acl_level'])
199 Session().commit()
199 Session().commit()
200 return {
200 return {
201 'msg': 'created new gist',
201 'msg': 'created new gist',
202 'gist': gist.get_api_data()
202 'gist': gist.get_api_data()
203 }
203 }
204 except Exception:
204 except Exception:
205 log.exception('Error occurred during creation of gist')
205 log.exception('Error occurred during creation of gist')
206 raise JSONRPCError('failed to create gist')
206 raise JSONRPCError('failed to create gist')
207
207
208
208
209 @jsonrpc_method()
209 @jsonrpc_method()
210 def delete_gist(request, apiuser, gistid):
210 def delete_gist(request, apiuser, gistid):
211 """
211 """
212 Deletes existing gist
212 Deletes existing gist
213
213
214 :param apiuser: filled automatically from apikey
214 :param apiuser: filled automatically from apikey
215 :type apiuser: AuthUser
215 :type apiuser: AuthUser
216 :param gistid: id of gist to delete
216 :param gistid: id of gist to delete
217 :type gistid: str
217 :type gistid: str
218
218
219 Example output:
219 Example output:
220
220
221 .. code-block:: bash
221 .. code-block:: bash
222
222
223 id : <id_given_in_input>
223 id : <id_given_in_input>
224 result : {
224 result : {
225 "deleted gist ID: <gist_id>",
225 "deleted gist ID: <gist_id>",
226 "gist": null
226 "gist": null
227 }
227 }
228 error : null
228 error : null
229
229
230 Example error output:
230 Example error output:
231
231
232 .. code-block:: bash
232 .. code-block:: bash
233
233
234 id : <id_given_in_input>
234 id : <id_given_in_input>
235 result : null
235 result : null
236 error : {
236 error : {
237 "failed to delete gist ID:<gist_id>"
237 "failed to delete gist ID:<gist_id>"
238 }
238 }
239
239
240 """
240 """
241
241
242 gist = get_gist_or_error(gistid)
242 gist = get_gist_or_error(gistid)
243 if not has_superadmin_permission(apiuser):
243 if not has_superadmin_permission(apiuser):
244 if gist.gist_owner != apiuser.user_id:
244 if gist.gist_owner != apiuser.user_id:
245 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
245 raise JSONRPCError('gist `%s` does not exist' % (gistid,))
246
246
247 try:
247 try:
248 GistModel().delete(gist)
248 GistModel().delete(gist)
249 Session().commit()
249 Session().commit()
250 return {
250 return {
251 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,),
251 'msg': 'deleted gist ID:%s' % (gist.gist_access_id,),
252 'gist': None
252 'gist': None
253 }
253 }
254 except Exception:
254 except Exception:
255 log.exception('Error occured during gist deletion')
255 log.exception('Error occured during gist deletion')
256 raise JSONRPCError('failed to delete gist ID:%s'
256 raise JSONRPCError('failed to delete gist ID:%s'
257 % (gist.gist_access_id,)) No newline at end of file
257 % (gist.gist_access_id,))
@@ -1,1113 +1,1113 b''
1
1
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2023 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23
23
24 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
24 from rhodecode.api import jsonrpc_method, JSONRPCError, JSONRPCValidationError
25 from rhodecode.api.utils import (
25 from rhodecode.api.utils import (
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
26 has_superadmin_permission, Optional, OAttr, get_repo_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
27 get_pull_request_or_error, get_commit_or_error, get_user_or_error,
28 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
28 validate_repo_permissions, resolve_ref_or_error, validate_set_owner_permissions)
29 from rhodecode.lib import channelstream
29 from rhodecode.lib import channelstream
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
30 from rhodecode.lib.auth import (HasRepoPermissionAnyApi)
31 from rhodecode.lib.base import vcs_operation_context
31 from rhodecode.lib.base import vcs_operation_context
32 from rhodecode.lib.utils2 import str2bool
32 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.lib.vcs.backends.base import unicode_to_reference
33 from rhodecode.lib.vcs.backends.base import unicode_to_reference
34 from rhodecode.model.changeset_status import ChangesetStatusModel
34 from rhodecode.model.changeset_status import ChangesetStatusModel
35 from rhodecode.model.comment import CommentsModel
35 from rhodecode.model.comment import CommentsModel
36 from rhodecode.model.db import (
36 from rhodecode.model.db import (
37 Session, ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers)
37 Session, ChangesetStatus, ChangesetComment, PullRequest, PullRequestReviewers)
38 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
38 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
39 from rhodecode.model.settings import SettingsModel
39 from rhodecode.model.settings import SettingsModel
40 from rhodecode.model.validation_schema import Invalid
40 from rhodecode.model.validation_schema import Invalid
41 from rhodecode.model.validation_schema.schemas.reviewer_schema import ReviewerListSchema
41 from rhodecode.model.validation_schema.schemas.reviewer_schema import ReviewerListSchema
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 @jsonrpc_method()
46 @jsonrpc_method()
47 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None),
47 def get_pull_request(request, apiuser, pullrequestid, repoid=Optional(None),
48 merge_state=Optional(False)):
48 merge_state=Optional(False)):
49 """
49 """
50 Get a pull request based on the given ID.
50 Get a pull request based on the given ID.
51
51
52 :param apiuser: This is filled automatically from the |authtoken|.
52 :param apiuser: This is filled automatically from the |authtoken|.
53 :type apiuser: AuthUser
53 :type apiuser: AuthUser
54 :param repoid: Optional, repository name or repository ID from where
54 :param repoid: Optional, repository name or repository ID from where
55 the pull request was opened.
55 the pull request was opened.
56 :type repoid: str or int
56 :type repoid: str or int
57 :param pullrequestid: ID of the requested pull request.
57 :param pullrequestid: ID of the requested pull request.
58 :type pullrequestid: int
58 :type pullrequestid: int
59 :param merge_state: Optional calculate merge state for each repository.
59 :param merge_state: Optional calculate merge state for each repository.
60 This could result in longer time to fetch the data
60 This could result in longer time to fetch the data
61 :type merge_state: bool
61 :type merge_state: bool
62
62
63 Example output:
63 Example output:
64
64
65 .. code-block:: bash
65 .. code-block:: bash
66
66
67 "id": <id_given_in_input>,
67 "id": <id_given_in_input>,
68 "result":
68 "result":
69 {
69 {
70 "pull_request_id": "<pull_request_id>",
70 "pull_request_id": "<pull_request_id>",
71 "url": "<url>",
71 "url": "<url>",
72 "title": "<title>",
72 "title": "<title>",
73 "description": "<description>",
73 "description": "<description>",
74 "status" : "<status>",
74 "status" : "<status>",
75 "created_on": "<date_time_created>",
75 "created_on": "<date_time_created>",
76 "updated_on": "<date_time_updated>",
76 "updated_on": "<date_time_updated>",
77 "versions": "<number_or_versions_of_pr>",
77 "versions": "<number_or_versions_of_pr>",
78 "commit_ids": [
78 "commit_ids": [
79 ...
79 ...
80 "<commit_id>",
80 "<commit_id>",
81 "<commit_id>",
81 "<commit_id>",
82 ...
82 ...
83 ],
83 ],
84 "review_status": "<review_status>",
84 "review_status": "<review_status>",
85 "mergeable": {
85 "mergeable": {
86 "status": "<bool>",
86 "status": "<bool>",
87 "message": "<message>",
87 "message": "<message>",
88 },
88 },
89 "source": {
89 "source": {
90 "clone_url": "<clone_url>",
90 "clone_url": "<clone_url>",
91 "repository": "<repository_name>",
91 "repository": "<repository_name>",
92 "reference":
92 "reference":
93 {
93 {
94 "name": "<name>",
94 "name": "<name>",
95 "type": "<type>",
95 "type": "<type>",
96 "commit_id": "<commit_id>",
96 "commit_id": "<commit_id>",
97 }
97 }
98 },
98 },
99 "target": {
99 "target": {
100 "clone_url": "<clone_url>",
100 "clone_url": "<clone_url>",
101 "repository": "<repository_name>",
101 "repository": "<repository_name>",
102 "reference":
102 "reference":
103 {
103 {
104 "name": "<name>",
104 "name": "<name>",
105 "type": "<type>",
105 "type": "<type>",
106 "commit_id": "<commit_id>",
106 "commit_id": "<commit_id>",
107 }
107 }
108 },
108 },
109 "merge": {
109 "merge": {
110 "clone_url": "<clone_url>",
110 "clone_url": "<clone_url>",
111 "reference":
111 "reference":
112 {
112 {
113 "name": "<name>",
113 "name": "<name>",
114 "type": "<type>",
114 "type": "<type>",
115 "commit_id": "<commit_id>",
115 "commit_id": "<commit_id>",
116 }
116 }
117 },
117 },
118 "author": <user_obj>,
118 "author": <user_obj>,
119 "reviewers": [
119 "reviewers": [
120 ...
120 ...
121 {
121 {
122 "user": "<user_obj>",
122 "user": "<user_obj>",
123 "review_status": "<review_status>",
123 "review_status": "<review_status>",
124 }
124 }
125 ...
125 ...
126 ]
126 ]
127 },
127 },
128 "error": null
128 "error": null
129 """
129 """
130
130
131 pull_request = get_pull_request_or_error(pullrequestid)
131 pull_request = get_pull_request_or_error(pullrequestid)
132 if Optional.extract(repoid):
132 if Optional.extract(repoid):
133 repo = get_repo_or_error(repoid)
133 repo = get_repo_or_error(repoid)
134 else:
134 else:
135 repo = pull_request.target_repo
135 repo = pull_request.target_repo
136
136
137 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
137 if not PullRequestModel().check_user_read(pull_request, apiuser, api=True):
138 raise JSONRPCError('repository `%s` or pull request `%s` '
138 raise JSONRPCError('repository `%s` or pull request `%s` '
139 'does not exist' % (repoid, pullrequestid))
139 'does not exist' % (repoid, pullrequestid))
140
140
141 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
141 # NOTE(marcink): only calculate and return merge state if the pr state is 'created'
142 # otherwise we can lock the repo on calculation of merge state while update/merge
142 # otherwise we can lock the repo on calculation of merge state while update/merge
143 # is happening.
143 # is happening.
144 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
144 pr_created = pull_request.pull_request_state == pull_request.STATE_CREATED
145 merge_state = Optional.extract(merge_state, binary=True) and pr_created
145 merge_state = Optional.extract(merge_state, binary=True) and pr_created
146 data = pull_request.get_api_data(with_merge_state=merge_state)
146 data = pull_request.get_api_data(with_merge_state=merge_state)
147 return data
147 return data
148
148
149
149
150 @jsonrpc_method()
150 @jsonrpc_method()
151 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
151 def get_pull_requests(request, apiuser, repoid, status=Optional('new'),
152 merge_state=Optional(False)):
152 merge_state=Optional(False)):
153 """
153 """
154 Get all pull requests from the repository specified in `repoid`.
154 Get all pull requests from the repository specified in `repoid`.
155
155
156 :param apiuser: This is filled automatically from the |authtoken|.
156 :param apiuser: This is filled automatically from the |authtoken|.
157 :type apiuser: AuthUser
157 :type apiuser: AuthUser
158 :param repoid: Optional repository name or repository ID.
158 :param repoid: Optional repository name or repository ID.
159 :type repoid: str or int
159 :type repoid: str or int
160 :param status: Only return pull requests with the specified status.
160 :param status: Only return pull requests with the specified status.
161 Valid options are.
161 Valid options are.
162 * ``new`` (default)
162 * ``new`` (default)
163 * ``open``
163 * ``open``
164 * ``closed``
164 * ``closed``
165 :type status: str
165 :type status: str
166 :param merge_state: Optional calculate merge state for each repository.
166 :param merge_state: Optional calculate merge state for each repository.
167 This could result in longer time to fetch the data
167 This could result in longer time to fetch the data
168 :type merge_state: bool
168 :type merge_state: bool
169
169
170 Example output:
170 Example output:
171
171
172 .. code-block:: bash
172 .. code-block:: bash
173
173
174 "id": <id_given_in_input>,
174 "id": <id_given_in_input>,
175 "result":
175 "result":
176 [
176 [
177 ...
177 ...
178 {
178 {
179 "pull_request_id": "<pull_request_id>",
179 "pull_request_id": "<pull_request_id>",
180 "url": "<url>",
180 "url": "<url>",
181 "title" : "<title>",
181 "title" : "<title>",
182 "description": "<description>",
182 "description": "<description>",
183 "status": "<status>",
183 "status": "<status>",
184 "created_on": "<date_time_created>",
184 "created_on": "<date_time_created>",
185 "updated_on": "<date_time_updated>",
185 "updated_on": "<date_time_updated>",
186 "commit_ids": [
186 "commit_ids": [
187 ...
187 ...
188 "<commit_id>",
188 "<commit_id>",
189 "<commit_id>",
189 "<commit_id>",
190 ...
190 ...
191 ],
191 ],
192 "review_status": "<review_status>",
192 "review_status": "<review_status>",
193 "mergeable": {
193 "mergeable": {
194 "status": "<bool>",
194 "status": "<bool>",
195 "message: "<message>",
195 "message: "<message>",
196 },
196 },
197 "source": {
197 "source": {
198 "clone_url": "<clone_url>",
198 "clone_url": "<clone_url>",
199 "reference":
199 "reference":
200 {
200 {
201 "name": "<name>",
201 "name": "<name>",
202 "type": "<type>",
202 "type": "<type>",
203 "commit_id": "<commit_id>",
203 "commit_id": "<commit_id>",
204 }
204 }
205 },
205 },
206 "target": {
206 "target": {
207 "clone_url": "<clone_url>",
207 "clone_url": "<clone_url>",
208 "reference":
208 "reference":
209 {
209 {
210 "name": "<name>",
210 "name": "<name>",
211 "type": "<type>",
211 "type": "<type>",
212 "commit_id": "<commit_id>",
212 "commit_id": "<commit_id>",
213 }
213 }
214 },
214 },
215 "merge": {
215 "merge": {
216 "clone_url": "<clone_url>",
216 "clone_url": "<clone_url>",
217 "reference":
217 "reference":
218 {
218 {
219 "name": "<name>",
219 "name": "<name>",
220 "type": "<type>",
220 "type": "<type>",
221 "commit_id": "<commit_id>",
221 "commit_id": "<commit_id>",
222 }
222 }
223 },
223 },
224 "author": <user_obj>,
224 "author": <user_obj>,
225 "reviewers": [
225 "reviewers": [
226 ...
226 ...
227 {
227 {
228 "user": "<user_obj>",
228 "user": "<user_obj>",
229 "review_status": "<review_status>",
229 "review_status": "<review_status>",
230 }
230 }
231 ...
231 ...
232 ]
232 ]
233 }
233 }
234 ...
234 ...
235 ],
235 ],
236 "error": null
236 "error": null
237
237
238 """
238 """
239 repo = get_repo_or_error(repoid)
239 repo = get_repo_or_error(repoid)
240 if not has_superadmin_permission(apiuser):
240 if not has_superadmin_permission(apiuser):
241 _perms = (
241 _perms = (
242 'repository.admin', 'repository.write', 'repository.read',)
242 'repository.admin', 'repository.write', 'repository.read',)
243 validate_repo_permissions(apiuser, repoid, repo, _perms)
243 validate_repo_permissions(apiuser, repoid, repo, _perms)
244
244
245 status = Optional.extract(status)
245 status = Optional.extract(status)
246 merge_state = Optional.extract(merge_state, binary=True)
246 merge_state = Optional.extract(merge_state, binary=True)
247 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
247 pull_requests = PullRequestModel().get_all(repo, statuses=[status],
248 order_by='id', order_dir='desc')
248 order_by='id', order_dir='desc')
249 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
249 data = [pr.get_api_data(with_merge_state=merge_state) for pr in pull_requests]
250 return data
250 return data
251
251
252
252
253 @jsonrpc_method()
253 @jsonrpc_method()
254 def merge_pull_request(
254 def merge_pull_request(
255 request, apiuser, pullrequestid, repoid=Optional(None),
255 request, apiuser, pullrequestid, repoid=Optional(None),
256 userid=Optional(OAttr('apiuser'))):
256 userid=Optional(OAttr('apiuser'))):
257 """
257 """
258 Merge the pull request specified by `pullrequestid` into its target
258 Merge the pull request specified by `pullrequestid` into its target
259 repository.
259 repository.
260
260
261 :param apiuser: This is filled automatically from the |authtoken|.
261 :param apiuser: This is filled automatically from the |authtoken|.
262 :type apiuser: AuthUser
262 :type apiuser: AuthUser
263 :param repoid: Optional, repository name or repository ID of the
263 :param repoid: Optional, repository name or repository ID of the
264 target repository to which the |pr| is to be merged.
264 target repository to which the |pr| is to be merged.
265 :type repoid: str or int
265 :type repoid: str or int
266 :param pullrequestid: ID of the pull request which shall be merged.
266 :param pullrequestid: ID of the pull request which shall be merged.
267 :type pullrequestid: int
267 :type pullrequestid: int
268 :param userid: Merge the pull request as this user.
268 :param userid: Merge the pull request as this user.
269 :type userid: Optional(str or int)
269 :type userid: Optional(str or int)
270
270
271 Example output:
271 Example output:
272
272
273 .. code-block:: bash
273 .. code-block:: bash
274
274
275 "id": <id_given_in_input>,
275 "id": <id_given_in_input>,
276 "result": {
276 "result": {
277 "executed": "<bool>",
277 "executed": "<bool>",
278 "failure_reason": "<int>",
278 "failure_reason": "<int>",
279 "merge_status_message": "<str>",
279 "merge_status_message": "<str>",
280 "merge_commit_id": "<merge_commit_id>",
280 "merge_commit_id": "<merge_commit_id>",
281 "possible": "<bool>",
281 "possible": "<bool>",
282 "merge_ref": {
282 "merge_ref": {
283 "commit_id": "<commit_id>",
283 "commit_id": "<commit_id>",
284 "type": "<type>",
284 "type": "<type>",
285 "name": "<name>"
285 "name": "<name>"
286 }
286 }
287 },
287 },
288 "error": null
288 "error": null
289 """
289 """
290 pull_request = get_pull_request_or_error(pullrequestid)
290 pull_request = get_pull_request_or_error(pullrequestid)
291 if Optional.extract(repoid):
291 if Optional.extract(repoid):
292 repo = get_repo_or_error(repoid)
292 repo = get_repo_or_error(repoid)
293 else:
293 else:
294 repo = pull_request.target_repo
294 repo = pull_request.target_repo
295 auth_user = apiuser
295 auth_user = apiuser
296
296
297 if not isinstance(userid, Optional):
297 if not isinstance(userid, Optional):
298 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
298 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
299 user=apiuser, repo_name=repo.repo_name)
299 user=apiuser, repo_name=repo.repo_name)
300 if has_superadmin_permission(apiuser) or is_repo_admin:
300 if has_superadmin_permission(apiuser) or is_repo_admin:
301 apiuser = get_user_or_error(userid)
301 apiuser = get_user_or_error(userid)
302 auth_user = apiuser.AuthUser()
302 auth_user = apiuser.AuthUser()
303 else:
303 else:
304 raise JSONRPCError('userid is not the same as your user')
304 raise JSONRPCError('userid is not the same as your user')
305
305
306 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
306 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
307 raise JSONRPCError(
307 raise JSONRPCError(
308 'Operation forbidden because pull request is in state {}, '
308 'Operation forbidden because pull request is in state {}, '
309 'only state {} is allowed.'.format(
309 'only state {} is allowed.'.format(
310 pull_request.pull_request_state, PullRequest.STATE_CREATED))
310 pull_request.pull_request_state, PullRequest.STATE_CREATED))
311
311
312 with pull_request.set_state(PullRequest.STATE_UPDATING):
312 with pull_request.set_state(PullRequest.STATE_UPDATING):
313 check = MergeCheck.validate(pull_request, auth_user=auth_user,
313 check = MergeCheck.validate(pull_request, auth_user=auth_user,
314 translator=request.translate)
314 translator=request.translate)
315 merge_possible = not check.failed
315 merge_possible = not check.failed
316
316
317 if not merge_possible:
317 if not merge_possible:
318 error_messages = []
318 error_messages = []
319 for err_type, error_msg in check.errors:
319 for err_type, error_msg in check.errors:
320 error_msg = request.translate(error_msg)
320 error_msg = request.translate(error_msg)
321 error_messages.append(error_msg)
321 error_messages.append(error_msg)
322
322
323 reasons = ','.join(error_messages)
323 reasons = ','.join(error_messages)
324 raise JSONRPCError(
324 raise JSONRPCError(
325 'merge not possible for following reasons: {}'.format(reasons))
325 'merge not possible for following reasons: {}'.format(reasons))
326
326
327 target_repo = pull_request.target_repo
327 target_repo = pull_request.target_repo
328 extras = vcs_operation_context(
328 extras = vcs_operation_context(
329 request.environ, repo_name=target_repo.repo_name,
329 request.environ, repo_name=target_repo.repo_name,
330 username=auth_user.username, action='push',
330 username=auth_user.username, action='push',
331 scm=target_repo.repo_type)
331 scm=target_repo.repo_type)
332 with pull_request.set_state(PullRequest.STATE_UPDATING):
332 with pull_request.set_state(PullRequest.STATE_UPDATING):
333 merge_response = PullRequestModel().merge_repo(
333 merge_response = PullRequestModel().merge_repo(
334 pull_request, apiuser, extras=extras)
334 pull_request, apiuser, extras=extras)
335 if merge_response.executed:
335 if merge_response.executed:
336 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
336 PullRequestModel().close_pull_request(pull_request.pull_request_id, auth_user)
337
337
338 Session().commit()
338 Session().commit()
339
339
340 # In previous versions the merge response directly contained the merge
340 # In previous versions the merge response directly contained the merge
341 # commit id. It is now contained in the merge reference object. To be
341 # commit id. It is now contained in the merge reference object. To be
342 # backwards compatible we have to extract it again.
342 # backwards compatible we have to extract it again.
343 merge_response = merge_response.asdict()
343 merge_response = merge_response.asdict()
344 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
344 merge_response['merge_commit_id'] = merge_response['merge_ref'].commit_id
345
345
346 return merge_response
346 return merge_response
347
347
348
348
349 @jsonrpc_method()
349 @jsonrpc_method()
350 def get_pull_request_comments(
350 def get_pull_request_comments(
351 request, apiuser, pullrequestid, repoid=Optional(None)):
351 request, apiuser, pullrequestid, repoid=Optional(None)):
352 """
352 """
353 Get all comments of pull request specified with the `pullrequestid`
353 Get all comments of pull request specified with the `pullrequestid`
354
354
355 :param apiuser: This is filled automatically from the |authtoken|.
355 :param apiuser: This is filled automatically from the |authtoken|.
356 :type apiuser: AuthUser
356 :type apiuser: AuthUser
357 :param repoid: Optional repository name or repository ID.
357 :param repoid: Optional repository name or repository ID.
358 :type repoid: str or int
358 :type repoid: str or int
359 :param pullrequestid: The pull request ID.
359 :param pullrequestid: The pull request ID.
360 :type pullrequestid: int
360 :type pullrequestid: int
361
361
362 Example output:
362 Example output:
363
363
364 .. code-block:: bash
364 .. code-block:: bash
365
365
366 id : <id_given_in_input>
366 id : <id_given_in_input>
367 result : [
367 result : [
368 {
368 {
369 "comment_author": {
369 "comment_author": {
370 "active": true,
370 "active": true,
371 "full_name_or_username": "Tom Gore",
371 "full_name_or_username": "Tom Gore",
372 "username": "admin"
372 "username": "admin"
373 },
373 },
374 "comment_created_on": "2017-01-02T18:43:45.533",
374 "comment_created_on": "2017-01-02T18:43:45.533",
375 "comment_f_path": null,
375 "comment_f_path": null,
376 "comment_id": 25,
376 "comment_id": 25,
377 "comment_lineno": null,
377 "comment_lineno": null,
378 "comment_status": {
378 "comment_status": {
379 "status": "under_review",
379 "status": "under_review",
380 "status_lbl": "Under Review"
380 "status_lbl": "Under Review"
381 },
381 },
382 "comment_text": "Example text",
382 "comment_text": "Example text",
383 "comment_type": null,
383 "comment_type": null,
384 "comment_last_version: 0,
384 "comment_last_version: 0,
385 "pull_request_version": null,
385 "pull_request_version": null,
386 "comment_commit_id": None,
386 "comment_commit_id": None,
387 "comment_pull_request_id": <pull_request_id>
387 "comment_pull_request_id": <pull_request_id>
388 }
388 }
389 ],
389 ],
390 error : null
390 error : null
391 """
391 """
392
392
393 pull_request = get_pull_request_or_error(pullrequestid)
393 pull_request = get_pull_request_or_error(pullrequestid)
394 if Optional.extract(repoid):
394 if Optional.extract(repoid):
395 repo = get_repo_or_error(repoid)
395 repo = get_repo_or_error(repoid)
396 else:
396 else:
397 repo = pull_request.target_repo
397 repo = pull_request.target_repo
398
398
399 if not PullRequestModel().check_user_read(
399 if not PullRequestModel().check_user_read(
400 pull_request, apiuser, api=True):
400 pull_request, apiuser, api=True):
401 raise JSONRPCError('repository `%s` or pull request `%s` '
401 raise JSONRPCError('repository `%s` or pull request `%s` '
402 'does not exist' % (repoid, pullrequestid))
402 'does not exist' % (repoid, pullrequestid))
403
403
404 (pull_request_latest,
404 (pull_request_latest,
405 pull_request_at_ver,
405 pull_request_at_ver,
406 pull_request_display_obj,
406 pull_request_display_obj,
407 at_version) = PullRequestModel().get_pr_version(
407 at_version) = PullRequestModel().get_pr_version(
408 pull_request.pull_request_id, version=None)
408 pull_request.pull_request_id, version=None)
409
409
410 versions = pull_request_display_obj.versions()
410 versions = pull_request_display_obj.versions()
411 ver_map = {
411 ver_map = {
412 ver.pull_request_version_id: cnt
412 ver.pull_request_version_id: cnt
413 for cnt, ver in enumerate(versions, 1)
413 for cnt, ver in enumerate(versions, 1)
414 }
414 }
415
415
416 # GENERAL COMMENTS with versions #
416 # GENERAL COMMENTS with versions #
417 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
417 q = CommentsModel()._all_general_comments_of_pull_request(pull_request)
418 q = q.order_by(ChangesetComment.comment_id.asc())
418 q = q.order_by(ChangesetComment.comment_id.asc())
419 general_comments = q.all()
419 general_comments = q.all()
420
420
421 # INLINE COMMENTS with versions #
421 # INLINE COMMENTS with versions #
422 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
422 q = CommentsModel()._all_inline_comments_of_pull_request(pull_request)
423 q = q.order_by(ChangesetComment.comment_id.asc())
423 q = q.order_by(ChangesetComment.comment_id.asc())
424 inline_comments = q.all()
424 inline_comments = q.all()
425
425
426 data = []
426 data = []
427 for comment in inline_comments + general_comments:
427 for comment in inline_comments + general_comments:
428 full_data = comment.get_api_data()
428 full_data = comment.get_api_data()
429 pr_version_id = None
429 pr_version_id = None
430 if comment.pull_request_version_id:
430 if comment.pull_request_version_id:
431 pr_version_id = 'v{}'.format(
431 pr_version_id = 'v{}'.format(
432 ver_map[comment.pull_request_version_id])
432 ver_map[comment.pull_request_version_id])
433
433
434 # sanitize some entries
434 # sanitize some entries
435
435
436 full_data['pull_request_version'] = pr_version_id
436 full_data['pull_request_version'] = pr_version_id
437 full_data['comment_author'] = {
437 full_data['comment_author'] = {
438 'username': full_data['comment_author'].username,
438 'username': full_data['comment_author'].username,
439 'full_name_or_username': full_data['comment_author'].full_name_or_username,
439 'full_name_or_username': full_data['comment_author'].full_name_or_username,
440 'active': full_data['comment_author'].active,
440 'active': full_data['comment_author'].active,
441 }
441 }
442
442
443 if full_data['comment_status']:
443 if full_data['comment_status']:
444 full_data['comment_status'] = {
444 full_data['comment_status'] = {
445 'status': full_data['comment_status'][0].status,
445 'status': full_data['comment_status'][0].status,
446 'status_lbl': full_data['comment_status'][0].status_lbl,
446 'status_lbl': full_data['comment_status'][0].status_lbl,
447 }
447 }
448 else:
448 else:
449 full_data['comment_status'] = {}
449 full_data['comment_status'] = {}
450
450
451 data.append(full_data)
451 data.append(full_data)
452 return data
452 return data
453
453
454
454
455 @jsonrpc_method()
455 @jsonrpc_method()
456 def comment_pull_request(
456 def comment_pull_request(
457 request, apiuser, pullrequestid, repoid=Optional(None),
457 request, apiuser, pullrequestid, repoid=Optional(None),
458 message=Optional(None), commit_id=Optional(None), status=Optional(None),
458 message=Optional(None), commit_id=Optional(None), status=Optional(None),
459 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
459 comment_type=Optional(ChangesetComment.COMMENT_TYPE_NOTE),
460 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
460 resolves_comment_id=Optional(None), extra_recipients=Optional([]),
461 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
461 userid=Optional(OAttr('apiuser')), send_email=Optional(True)):
462 """
462 """
463 Comment on the pull request specified with the `pullrequestid`,
463 Comment on the pull request specified with the `pullrequestid`,
464 in the |repo| specified by the `repoid`, and optionally change the
464 in the |repo| specified by the `repoid`, and optionally change the
465 review status.
465 review status.
466
466
467 :param apiuser: This is filled automatically from the |authtoken|.
467 :param apiuser: This is filled automatically from the |authtoken|.
468 :type apiuser: AuthUser
468 :type apiuser: AuthUser
469 :param repoid: Optional repository name or repository ID.
469 :param repoid: Optional repository name or repository ID.
470 :type repoid: str or int
470 :type repoid: str or int
471 :param pullrequestid: The pull request ID.
471 :param pullrequestid: The pull request ID.
472 :type pullrequestid: int
472 :type pullrequestid: int
473 :param commit_id: Specify the commit_id for which to set a comment. If
473 :param commit_id: Specify the commit_id for which to set a comment. If
474 given commit_id is different than latest in the PR status
474 given commit_id is different than latest in the PR status
475 change won't be performed.
475 change won't be performed.
476 :type commit_id: str
476 :type commit_id: str
477 :param message: The text content of the comment.
477 :param message: The text content of the comment.
478 :type message: str
478 :type message: str
479 :param status: (**Optional**) Set the approval status of the pull
479 :param status: (**Optional**) Set the approval status of the pull
480 request. One of: 'not_reviewed', 'approved', 'rejected',
480 request. One of: 'not_reviewed', 'approved', 'rejected',
481 'under_review'
481 'under_review'
482 :type status: str
482 :type status: str
483 :param comment_type: Comment type, one of: 'note', 'todo'
483 :param comment_type: Comment type, one of: 'note', 'todo'
484 :type comment_type: Optional(str), default: 'note'
484 :type comment_type: Optional(str), default: 'note'
485 :param resolves_comment_id: id of comment which this one will resolve
485 :param resolves_comment_id: id of comment which this one will resolve
486 :type resolves_comment_id: Optional(int)
486 :type resolves_comment_id: Optional(int)
487 :param extra_recipients: list of user ids or usernames to add
487 :param extra_recipients: list of user ids or usernames to add
488 notifications for this comment. Acts like a CC for notification
488 notifications for this comment. Acts like a CC for notification
489 :type extra_recipients: Optional(list)
489 :type extra_recipients: Optional(list)
490 :param userid: Comment on the pull request as this user
490 :param userid: Comment on the pull request as this user
491 :type userid: Optional(str or int)
491 :type userid: Optional(str or int)
492 :param send_email: Define if this comment should also send email notification
492 :param send_email: Define if this comment should also send email notification
493 :type send_email: Optional(bool)
493 :type send_email: Optional(bool)
494
494
495 Example output:
495 Example output:
496
496
497 .. code-block:: bash
497 .. code-block:: bash
498
498
499 id : <id_given_in_input>
499 id : <id_given_in_input>
500 result : {
500 result : {
501 "pull_request_id": "<Integer>",
501 "pull_request_id": "<Integer>",
502 "comment_id": "<Integer>",
502 "comment_id": "<Integer>",
503 "status": {"given": <given_status>,
503 "status": {"given": <given_status>,
504 "was_changed": <bool status_was_actually_changed> },
504 "was_changed": <bool status_was_actually_changed> },
505 },
505 },
506 error : null
506 error : null
507 """
507 """
508 _ = request.translate
508 _ = request.translate
509
509
510 pull_request = get_pull_request_or_error(pullrequestid)
510 pull_request = get_pull_request_or_error(pullrequestid)
511 if Optional.extract(repoid):
511 if Optional.extract(repoid):
512 repo = get_repo_or_error(repoid)
512 repo = get_repo_or_error(repoid)
513 else:
513 else:
514 repo = pull_request.target_repo
514 repo = pull_request.target_repo
515
515
516 db_repo_name = repo.repo_name
516 db_repo_name = repo.repo_name
517 auth_user = apiuser
517 auth_user = apiuser
518 if not isinstance(userid, Optional):
518 if not isinstance(userid, Optional):
519 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
519 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
520 user=apiuser, repo_name=db_repo_name)
520 user=apiuser, repo_name=db_repo_name)
521 if has_superadmin_permission(apiuser) or is_repo_admin:
521 if has_superadmin_permission(apiuser) or is_repo_admin:
522 apiuser = get_user_or_error(userid)
522 apiuser = get_user_or_error(userid)
523 auth_user = apiuser.AuthUser()
523 auth_user = apiuser.AuthUser()
524 else:
524 else:
525 raise JSONRPCError('userid is not the same as your user')
525 raise JSONRPCError('userid is not the same as your user')
526
526
527 if pull_request.is_closed():
527 if pull_request.is_closed():
528 raise JSONRPCError(f'pull request `{pullrequestid}` comment failed, pull request is closed')
528 raise JSONRPCError(f'pull request `{pullrequestid}` comment failed, pull request is closed')
529
529
530 if not PullRequestModel().check_user_read(
530 if not PullRequestModel().check_user_read(
531 pull_request, apiuser, api=True):
531 pull_request, apiuser, api=True):
532 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
532 raise JSONRPCError('repository `%s` does not exist' % (repoid,))
533 message = Optional.extract(message)
533 message = Optional.extract(message)
534 status = Optional.extract(status)
534 status = Optional.extract(status)
535 commit_id = Optional.extract(commit_id)
535 commit_id = Optional.extract(commit_id)
536 comment_type = Optional.extract(comment_type)
536 comment_type = Optional.extract(comment_type)
537 resolves_comment_id = Optional.extract(resolves_comment_id)
537 resolves_comment_id = Optional.extract(resolves_comment_id)
538 extra_recipients = Optional.extract(extra_recipients)
538 extra_recipients = Optional.extract(extra_recipients)
539 send_email = Optional.extract(send_email, binary=True)
539 send_email = Optional.extract(send_email, binary=True)
540
540
541 if not message and not status:
541 if not message and not status:
542 raise JSONRPCError(
542 raise JSONRPCError(
543 'Both message and status parameters are missing. '
543 'Both message and status parameters are missing. '
544 'At least one is required.')
544 'At least one is required.')
545
545
546 if status and status not in (st[0] for st in ChangesetStatus.STATUSES):
546 if status and status not in (st[0] for st in ChangesetStatus.STATUSES):
547 raise JSONRPCError(f'Unknown comment status: `{status}`')
547 raise JSONRPCError(f'Unknown comment status: `{status}`')
548
548
549 if commit_id and commit_id not in pull_request.revisions:
549 if commit_id and commit_id not in pull_request.revisions:
550 raise JSONRPCError(f'Invalid commit_id `{commit_id}` for this pull request.')
550 raise JSONRPCError(f'Invalid commit_id `{commit_id}` for this pull request.')
551
551
552 allowed_to_change_status = PullRequestModel().check_user_change_status(
552 allowed_to_change_status = PullRequestModel().check_user_change_status(
553 pull_request, apiuser)
553 pull_request, apiuser)
554
554
555 # if commit_id is passed re-validated if user is allowed to change status
555 # if commit_id is passed re-validated if user is allowed to change status
556 # based on the latest commit_id from the PR
556 # based on the latest commit_id from the PR
557 if commit_id:
557 if commit_id:
558 commit_idx = pull_request.revisions.index(commit_id)
558 commit_idx = pull_request.revisions.index(commit_id)
559 if commit_idx != 0:
559 if commit_idx != 0:
560 log.warning('Resetting allowed_to_change_status = False because commit is NOT the latest in pull-request')
560 log.warning('Resetting allowed_to_change_status = False because commit is NOT the latest in pull-request')
561 allowed_to_change_status = False
561 allowed_to_change_status = False
562
562
563 if resolves_comment_id:
563 if resolves_comment_id:
564 comment = ChangesetComment.get(resolves_comment_id)
564 comment = ChangesetComment.get(resolves_comment_id)
565 if not comment:
565 if not comment:
566 raise JSONRPCError(f'Invalid resolves_comment_id `{resolves_comment_id}` for this pull request.')
566 raise JSONRPCError(f'Invalid resolves_comment_id `{resolves_comment_id}` for this pull request.')
567 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
567 if comment.comment_type != ChangesetComment.COMMENT_TYPE_TODO:
568 raise JSONRPCError(f'Comment `{resolves_comment_id}` is wrong type for setting status to resolved.')
568 raise JSONRPCError(f'Comment `{resolves_comment_id}` is wrong type for setting status to resolved.')
569
569
570 text = message
570 text = message
571 status_label = ChangesetStatus.get_status_lbl(status)
571 status_label = ChangesetStatus.get_status_lbl(status)
572 if status and allowed_to_change_status:
572 if status and allowed_to_change_status:
573 st_message = ('Status change %(transition_icon)s %(status)s'
573 st_message = ('Status change %(transition_icon)s %(status)s'
574 % {'transition_icon': '>', 'status': status_label})
574 % {'transition_icon': '>', 'status': status_label})
575 text = message or st_message
575 text = message or st_message
576
576
577 rc_config = SettingsModel().get_all_settings()
577 rc_config = SettingsModel().get_all_settings()
578 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
578 renderer = rc_config.get('rhodecode_markup_renderer', 'rst')
579
579
580 status_change = status and allowed_to_change_status
580 status_change = status and allowed_to_change_status
581 comment = CommentsModel().create(
581 comment = CommentsModel().create(
582 text=text,
582 text=text,
583 repo=pull_request.target_repo.repo_id,
583 repo=pull_request.target_repo.repo_id,
584 user=apiuser.user_id,
584 user=apiuser.user_id,
585 pull_request=pull_request.pull_request_id,
585 pull_request=pull_request.pull_request_id,
586 f_path=None,
586 f_path=None,
587 line_no=None,
587 line_no=None,
588 status_change=(status_label if status_change else None),
588 status_change=(status_label if status_change else None),
589 status_change_type=(status if status_change else None),
589 status_change_type=(status if status_change else None),
590 closing_pr=False,
590 closing_pr=False,
591 renderer=renderer,
591 renderer=renderer,
592 comment_type=comment_type,
592 comment_type=comment_type,
593 resolves_comment_id=resolves_comment_id,
593 resolves_comment_id=resolves_comment_id,
594 auth_user=auth_user,
594 auth_user=auth_user,
595 extra_recipients=extra_recipients,
595 extra_recipients=extra_recipients,
596 send_email=send_email
596 send_email=send_email
597 )
597 )
598
598
599 is_inline = comment.is_inline
599 is_inline = comment.is_inline
600
600
601 if allowed_to_change_status and status:
601 if allowed_to_change_status and status:
602 old_calculated_status = pull_request.calculated_review_status()
602 old_calculated_status = pull_request.calculated_review_status()
603 ChangesetStatusModel().set_status(
603 ChangesetStatusModel().set_status(
604 pull_request.target_repo.repo_id,
604 pull_request.target_repo.repo_id,
605 status,
605 status,
606 apiuser.user_id,
606 apiuser.user_id,
607 comment,
607 comment,
608 pull_request=pull_request.pull_request_id
608 pull_request=pull_request.pull_request_id
609 )
609 )
610 Session().flush()
610 Session().flush()
611
611
612 Session().commit()
612 Session().commit()
613
613
614 PullRequestModel().trigger_pull_request_hook(
614 PullRequestModel().trigger_pull_request_hook(
615 pull_request, apiuser, 'comment',
615 pull_request, apiuser, 'comment',
616 data={'comment': comment})
616 data={'comment': comment})
617
617
618 if allowed_to_change_status and status:
618 if allowed_to_change_status and status:
619 # we now calculate the status of pull request, and based on that
619 # we now calculate the status of pull request, and based on that
620 # calculation we set the commits status
620 # calculation we set the commits status
621 calculated_status = pull_request.calculated_review_status()
621 calculated_status = pull_request.calculated_review_status()
622 if old_calculated_status != calculated_status:
622 if old_calculated_status != calculated_status:
623 PullRequestModel().trigger_pull_request_hook(
623 PullRequestModel().trigger_pull_request_hook(
624 pull_request, apiuser, 'review_status_change',
624 pull_request, apiuser, 'review_status_change',
625 data={'status': calculated_status})
625 data={'status': calculated_status})
626
626
627 data = {
627 data = {
628 'pull_request_id': pull_request.pull_request_id,
628 'pull_request_id': pull_request.pull_request_id,
629 'comment_id': comment.comment_id if comment else None,
629 'comment_id': comment.comment_id if comment else None,
630 'status': {'given': status, 'was_changed': status_change},
630 'status': {'given': status, 'was_changed': status_change},
631 }
631 }
632
632
633 comment_broadcast_channel = channelstream.comment_channel(
633 comment_broadcast_channel = channelstream.comment_channel(
634 db_repo_name, pull_request_obj=pull_request)
634 db_repo_name, pull_request_obj=pull_request)
635
635
636 comment_data = data
636 comment_data = data
637 comment_type = 'inline' if is_inline else 'general'
637 comment_type = 'inline' if is_inline else 'general'
638 channelstream.comment_channelstream_push(
638 channelstream.comment_channelstream_push(
639 request, comment_broadcast_channel, apiuser,
639 request, comment_broadcast_channel, apiuser,
640 _('posted a new {} comment').format(comment_type),
640 _('posted a new {} comment').format(comment_type),
641 comment_data=comment_data)
641 comment_data=comment_data)
642
642
643 return data
643 return data
644
644
645
645
646 def _reviewers_validation(obj_list):
646 def _reviewers_validation(obj_list):
647 schema = ReviewerListSchema()
647 schema = ReviewerListSchema()
648 try:
648 try:
649 reviewer_objects = schema.deserialize(obj_list)
649 reviewer_objects = schema.deserialize(obj_list)
650 except Invalid as err:
650 except Invalid as err:
651 raise JSONRPCValidationError(colander_exc=err)
651 raise JSONRPCValidationError(colander_exc=err)
652
652
653 # validate users
653 # validate users
654 for reviewer_object in reviewer_objects:
654 for reviewer_object in reviewer_objects:
655 user = get_user_or_error(reviewer_object['username'])
655 user = get_user_or_error(reviewer_object['username'])
656 reviewer_object['user_id'] = user.user_id
656 reviewer_object['user_id'] = user.user_id
657 return reviewer_objects
657 return reviewer_objects
658
658
659
659
660 @jsonrpc_method()
660 @jsonrpc_method()
661 def create_pull_request(
661 def create_pull_request(
662 request, apiuser, source_repo, target_repo, source_ref, target_ref,
662 request, apiuser, source_repo, target_repo, source_ref, target_ref,
663 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
663 owner=Optional(OAttr('apiuser')), title=Optional(''), description=Optional(''),
664 description_renderer=Optional(''),
664 description_renderer=Optional(''),
665 reviewers=Optional(None), observers=Optional(None)):
665 reviewers=Optional(None), observers=Optional(None)):
666 """
666 """
667 Creates a new pull request.
667 Creates a new pull request.
668
668
669 Accepts refs in the following formats:
669 Accepts refs in the following formats:
670
670
671 * branch:<branch_name>:<sha>
671 * branch:<branch_name>:<sha>
672 * branch:<branch_name>
672 * branch:<branch_name>
673 * bookmark:<bookmark_name>:<sha> (Mercurial only)
673 * bookmark:<bookmark_name>:<sha> (Mercurial only)
674 * bookmark:<bookmark_name> (Mercurial only)
674 * bookmark:<bookmark_name> (Mercurial only)
675
675
676 :param apiuser: This is filled automatically from the |authtoken|.
676 :param apiuser: This is filled automatically from the |authtoken|.
677 :type apiuser: AuthUser
677 :type apiuser: AuthUser
678 :param source_repo: Set the source repository name.
678 :param source_repo: Set the source repository name.
679 :type source_repo: str
679 :type source_repo: str
680 :param target_repo: Set the target repository name.
680 :param target_repo: Set the target repository name.
681 :type target_repo: str
681 :type target_repo: str
682 :param source_ref: Set the source ref name.
682 :param source_ref: Set the source ref name.
683 :type source_ref: str
683 :type source_ref: str
684 :param target_ref: Set the target ref name.
684 :param target_ref: Set the target ref name.
685 :type target_ref: str
685 :type target_ref: str
686 :param owner: user_id or username
686 :param owner: user_id or username
687 :type owner: Optional(str)
687 :type owner: Optional(str)
688 :param title: Optionally Set the pull request title, it's generated otherwise
688 :param title: Optionally Set the pull request title, it's generated otherwise
689 :type title: str
689 :type title: str
690 :param description: Set the pull request description.
690 :param description: Set the pull request description.
691 :type description: Optional(str)
691 :type description: Optional(str)
692 :type description_renderer: Optional(str)
692 :type description_renderer: Optional(str)
693 :param description_renderer: Set pull request renderer for the description.
693 :param description_renderer: Set pull request renderer for the description.
694 It should be 'rst', 'markdown' or 'plain'. If not give default
694 It should be 'rst', 'markdown' or 'plain'. If not give default
695 system renderer will be used
695 system renderer will be used
696 :param reviewers: Set the new pull request reviewers list.
696 :param reviewers: Set the new pull request reviewers list.
697 Reviewer defined by review rules will be added automatically to the
697 Reviewer defined by review rules will be added automatically to the
698 defined list.
698 defined list.
699 :type reviewers: Optional(list)
699 :type reviewers: Optional(list)
700 Accepts username strings or objects of the format:
700 Accepts username strings or objects of the format:
701
701
702 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
702 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
703 :param observers: Set the new pull request observers list.
703 :param observers: Set the new pull request observers list.
704 Reviewer defined by review rules will be added automatically to the
704 Reviewer defined by review rules will be added automatically to the
705 defined list. This feature is only available in RhodeCode EE
705 defined list. This feature is only available in RhodeCode EE
706 :type observers: Optional(list)
706 :type observers: Optional(list)
707 Accepts username strings or objects of the format:
707 Accepts username strings or objects of the format:
708
708
709 [{'username': 'nick', 'reasons': ['original author']}]
709 [{'username': 'nick', 'reasons': ['original author']}]
710 """
710 """
711
711
712 source_db_repo = get_repo_or_error(source_repo)
712 source_db_repo = get_repo_or_error(source_repo)
713 target_db_repo = get_repo_or_error(target_repo)
713 target_db_repo = get_repo_or_error(target_repo)
714 if not has_superadmin_permission(apiuser):
714 if not has_superadmin_permission(apiuser):
715 _perms = ('repository.admin', 'repository.write', 'repository.read',)
715 _perms = ('repository.admin', 'repository.write', 'repository.read',)
716 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
716 validate_repo_permissions(apiuser, source_repo, source_db_repo, _perms)
717
717
718 owner = validate_set_owner_permissions(apiuser, owner)
718 owner = validate_set_owner_permissions(apiuser, owner)
719
719
720 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
720 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
721 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
721 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
722
722
723 get_commit_or_error(full_source_ref, source_db_repo)
723 get_commit_or_error(full_source_ref, source_db_repo)
724 get_commit_or_error(full_target_ref, target_db_repo)
724 get_commit_or_error(full_target_ref, target_db_repo)
725
725
726 reviewer_objects = Optional.extract(reviewers) or []
726 reviewer_objects = Optional.extract(reviewers) or []
727 observer_objects = Optional.extract(observers) or []
727 observer_objects = Optional.extract(observers) or []
728
728
729 # serialize and validate passed in given reviewers
729 # serialize and validate passed in given reviewers
730 if reviewer_objects:
730 if reviewer_objects:
731 reviewer_objects = _reviewers_validation(reviewer_objects)
731 reviewer_objects = _reviewers_validation(reviewer_objects)
732
732
733 if observer_objects:
733 if observer_objects:
734 observer_objects = _reviewers_validation(reviewer_objects)
734 observer_objects = _reviewers_validation(reviewer_objects)
735
735
736 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
736 get_default_reviewers_data, validate_default_reviewers, validate_observers = \
737 PullRequestModel().get_reviewer_functions()
737 PullRequestModel().get_reviewer_functions()
738
738
739 source_ref_obj = unicode_to_reference(full_source_ref)
739 source_ref_obj = unicode_to_reference(full_source_ref)
740 target_ref_obj = unicode_to_reference(full_target_ref)
740 target_ref_obj = unicode_to_reference(full_target_ref)
741
741
742 # recalculate reviewers logic, to make sure we can validate this
742 # recalculate reviewers logic, to make sure we can validate this
743 default_reviewers_data = get_default_reviewers_data(
743 default_reviewers_data = get_default_reviewers_data(
744 owner,
744 owner,
745 source_db_repo,
745 source_db_repo,
746 source_ref_obj,
746 source_ref_obj,
747 target_db_repo,
747 target_db_repo,
748 target_ref_obj,
748 target_ref_obj,
749 )
749 )
750
750
751 # now MERGE our given with the calculated from the default rules
751 # now MERGE our given with the calculated from the default rules
752 just_reviewers = [
752 just_reviewers = [
753 x for x in default_reviewers_data['reviewers']
753 x for x in default_reviewers_data['reviewers']
754 if x['role'] == PullRequestReviewers.ROLE_REVIEWER]
754 if x['role'] == PullRequestReviewers.ROLE_REVIEWER]
755 reviewer_objects = just_reviewers + reviewer_objects
755 reviewer_objects = just_reviewers + reviewer_objects
756
756
757 try:
757 try:
758 reviewers = validate_default_reviewers(
758 reviewers = validate_default_reviewers(
759 reviewer_objects, default_reviewers_data)
759 reviewer_objects, default_reviewers_data)
760 except ValueError as e:
760 except ValueError as e:
761 raise JSONRPCError('Reviewers Validation: {}'.format(e))
761 raise JSONRPCError('Reviewers Validation: {}'.format(e))
762
762
763 # now MERGE our given with the calculated from the default rules
763 # now MERGE our given with the calculated from the default rules
764 just_observers = [
764 just_observers = [
765 x for x in default_reviewers_data['reviewers']
765 x for x in default_reviewers_data['reviewers']
766 if x['role'] == PullRequestReviewers.ROLE_OBSERVER]
766 if x['role'] == PullRequestReviewers.ROLE_OBSERVER]
767 observer_objects = just_observers + observer_objects
767 observer_objects = just_observers + observer_objects
768
768
769 try:
769 try:
770 observers = validate_observers(
770 observers = validate_observers(
771 observer_objects, default_reviewers_data)
771 observer_objects, default_reviewers_data)
772 except ValueError as e:
772 except ValueError as e:
773 raise JSONRPCError('Observer Validation: {}'.format(e))
773 raise JSONRPCError('Observer Validation: {}'.format(e))
774
774
775 title = Optional.extract(title)
775 title = Optional.extract(title)
776 if not title:
776 if not title:
777 title_source_ref = source_ref_obj.name
777 title_source_ref = source_ref_obj.name
778 title = PullRequestModel().generate_pullrequest_title(
778 title = PullRequestModel().generate_pullrequest_title(
779 source=source_repo,
779 source=source_repo,
780 source_ref=title_source_ref,
780 source_ref=title_source_ref,
781 target=target_repo
781 target=target_repo
782 )
782 )
783
783
784 diff_info = default_reviewers_data['diff_info']
784 diff_info = default_reviewers_data['diff_info']
785 common_ancestor_id = diff_info['ancestor']
785 common_ancestor_id = diff_info['ancestor']
786 # NOTE(marcink): reversed is consistent with how we open it in the WEB interface
786 # NOTE(marcink): reversed is consistent with how we open it in the WEB interface
787 commits = [commit['commit_id'] for commit in reversed(diff_info['commits'])]
787 commits = [commit['commit_id'] for commit in reversed(diff_info['commits'])]
788
788
789 if not common_ancestor_id:
789 if not common_ancestor_id:
790 raise JSONRPCError('no common ancestor found between specified references')
790 raise JSONRPCError('no common ancestor found between specified references')
791
791
792 if not commits:
792 if not commits:
793 raise JSONRPCError('no commits found for merge between specified references')
793 raise JSONRPCError('no commits found for merge between specified references')
794
794
795 # recalculate target ref based on ancestor
795 # recalculate target ref based on ancestor
796 full_target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, common_ancestor_id))
796 full_target_ref = ':'.join((target_ref_obj.type, target_ref_obj.name, common_ancestor_id))
797
797
798 # fetch renderer, if set fallback to plain in case of PR
798 # fetch renderer, if set fallback to plain in case of PR
799 rc_config = SettingsModel().get_all_settings()
799 rc_config = SettingsModel().get_all_settings()
800 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
800 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
801 description = Optional.extract(description)
801 description = Optional.extract(description)
802 description_renderer = Optional.extract(description_renderer) or default_system_renderer
802 description_renderer = Optional.extract(description_renderer) or default_system_renderer
803
803
804 pull_request = PullRequestModel().create(
804 pull_request = PullRequestModel().create(
805 created_by=owner.user_id,
805 created_by=owner.user_id,
806 source_repo=source_repo,
806 source_repo=source_repo,
807 source_ref=full_source_ref,
807 source_ref=full_source_ref,
808 target_repo=target_repo,
808 target_repo=target_repo,
809 target_ref=full_target_ref,
809 target_ref=full_target_ref,
810 common_ancestor_id=common_ancestor_id,
810 common_ancestor_id=common_ancestor_id,
811 revisions=commits,
811 revisions=commits,
812 reviewers=reviewers,
812 reviewers=reviewers,
813 observers=observers,
813 observers=observers,
814 title=title,
814 title=title,
815 description=description,
815 description=description,
816 description_renderer=description_renderer,
816 description_renderer=description_renderer,
817 reviewer_data=default_reviewers_data,
817 reviewer_data=default_reviewers_data,
818 auth_user=apiuser
818 auth_user=apiuser
819 )
819 )
820
820
821 Session().commit()
821 Session().commit()
822 data = {
822 data = {
823 'msg': 'Created new pull request `{}`'.format(title),
823 'msg': 'Created new pull request `{}`'.format(title),
824 'pull_request_id': pull_request.pull_request_id,
824 'pull_request_id': pull_request.pull_request_id,
825 }
825 }
826 return data
826 return data
827
827
828
828
829 @jsonrpc_method()
829 @jsonrpc_method()
830 def update_pull_request(
830 def update_pull_request(
831 request, apiuser, pullrequestid, repoid=Optional(None),
831 request, apiuser, pullrequestid, repoid=Optional(None),
832 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
832 title=Optional(''), description=Optional(''), description_renderer=Optional(''),
833 reviewers=Optional(None), observers=Optional(None), update_commits=Optional(None)):
833 reviewers=Optional(None), observers=Optional(None), update_commits=Optional(None)):
834 """
834 """
835 Updates a pull request.
835 Updates a pull request.
836
836
837 :param apiuser: This is filled automatically from the |authtoken|.
837 :param apiuser: This is filled automatically from the |authtoken|.
838 :type apiuser: AuthUser
838 :type apiuser: AuthUser
839 :param repoid: Optional repository name or repository ID.
839 :param repoid: Optional repository name or repository ID.
840 :type repoid: str or int
840 :type repoid: str or int
841 :param pullrequestid: The pull request ID.
841 :param pullrequestid: The pull request ID.
842 :type pullrequestid: int
842 :type pullrequestid: int
843 :param title: Set the pull request title.
843 :param title: Set the pull request title.
844 :type title: str
844 :type title: str
845 :param description: Update pull request description.
845 :param description: Update pull request description.
846 :type description: Optional(str)
846 :type description: Optional(str)
847 :type description_renderer: Optional(str)
847 :type description_renderer: Optional(str)
848 :param description_renderer: Update pull request renderer for the description.
848 :param description_renderer: Update pull request renderer for the description.
849 It should be 'rst', 'markdown' or 'plain'
849 It should be 'rst', 'markdown' or 'plain'
850 :param reviewers: Update pull request reviewers list with new value.
850 :param reviewers: Update pull request reviewers list with new value.
851 :type reviewers: Optional(list)
851 :type reviewers: Optional(list)
852 Accepts username strings or objects of the format:
852 Accepts username strings or objects of the format:
853
853
854 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
854 [{'username': 'nick', 'reasons': ['original author'], 'mandatory': <bool>}]
855 :param observers: Update pull request observers list with new value.
855 :param observers: Update pull request observers list with new value.
856 :type observers: Optional(list)
856 :type observers: Optional(list)
857 Accepts username strings or objects of the format:
857 Accepts username strings or objects of the format:
858
858
859 [{'username': 'nick', 'reasons': ['should be aware about this PR']}]
859 [{'username': 'nick', 'reasons': ['should be aware about this PR']}]
860 :param update_commits: Trigger update of commits for this pull request
860 :param update_commits: Trigger update of commits for this pull request
861 :type: update_commits: Optional(bool)
861 :type: update_commits: Optional(bool)
862
862
863 Example output:
863 Example output:
864
864
865 .. code-block:: bash
865 .. code-block:: bash
866
866
867 id : <id_given_in_input>
867 id : <id_given_in_input>
868 result : {
868 result : {
869 "msg": "Updated pull request `63`",
869 "msg": "Updated pull request `63`",
870 "pull_request": <pull_request_object>,
870 "pull_request": <pull_request_object>,
871 "updated_reviewers": {
871 "updated_reviewers": {
872 "added": [
872 "added": [
873 "username"
873 "username"
874 ],
874 ],
875 "removed": []
875 "removed": []
876 },
876 },
877 "updated_observers": {
877 "updated_observers": {
878 "added": [
878 "added": [
879 "username"
879 "username"
880 ],
880 ],
881 "removed": []
881 "removed": []
882 },
882 },
883 "updated_commits": {
883 "updated_commits": {
884 "added": [
884 "added": [
885 "<sha1_hash>"
885 "<sha1_hash>"
886 ],
886 ],
887 "common": [
887 "common": [
888 "<sha1_hash>",
888 "<sha1_hash>",
889 "<sha1_hash>",
889 "<sha1_hash>",
890 ],
890 ],
891 "removed": []
891 "removed": []
892 }
892 }
893 }
893 }
894 error : null
894 error : null
895 """
895 """
896
896
897 pull_request = get_pull_request_or_error(pullrequestid)
897 pull_request = get_pull_request_or_error(pullrequestid)
898 if Optional.extract(repoid):
898 if Optional.extract(repoid):
899 repo = get_repo_or_error(repoid)
899 repo = get_repo_or_error(repoid)
900 else:
900 else:
901 repo = pull_request.target_repo
901 repo = pull_request.target_repo
902
902
903 if not PullRequestModel().check_user_update(
903 if not PullRequestModel().check_user_update(
904 pull_request, apiuser, api=True):
904 pull_request, apiuser, api=True):
905 raise JSONRPCError(
905 raise JSONRPCError(
906 'pull request `%s` update failed, no permission to update.' % (
906 'pull request `%s` update failed, no permission to update.' % (
907 pullrequestid,))
907 pullrequestid,))
908 if pull_request.is_closed():
908 if pull_request.is_closed():
909 raise JSONRPCError(
909 raise JSONRPCError(
910 'pull request `%s` update failed, pull request is closed' % (
910 'pull request `%s` update failed, pull request is closed' % (
911 pullrequestid,))
911 pullrequestid,))
912
912
913 reviewer_objects = Optional.extract(reviewers) or []
913 reviewer_objects = Optional.extract(reviewers) or []
914 observer_objects = Optional.extract(observers) or []
914 observer_objects = Optional.extract(observers) or []
915
915
916 title = Optional.extract(title)
916 title = Optional.extract(title)
917 description = Optional.extract(description)
917 description = Optional.extract(description)
918 description_renderer = Optional.extract(description_renderer)
918 description_renderer = Optional.extract(description_renderer)
919
919
920 # Update title/description
920 # Update title/description
921 title_changed = False
921 title_changed = False
922 if title or description:
922 if title or description:
923 PullRequestModel().edit(
923 PullRequestModel().edit(
924 pull_request,
924 pull_request,
925 title or pull_request.title,
925 title or pull_request.title,
926 description or pull_request.description,
926 description or pull_request.description,
927 description_renderer or pull_request.description_renderer,
927 description_renderer or pull_request.description_renderer,
928 apiuser)
928 apiuser)
929 Session().commit()
929 Session().commit()
930 title_changed = True
930 title_changed = True
931
931
932 commit_changes = {"added": [], "common": [], "removed": []}
932 commit_changes = {"added": [], "common": [], "removed": []}
933
933
934 # Update commits
934 # Update commits
935 commits_changed = False
935 commits_changed = False
936 if str2bool(Optional.extract(update_commits)):
936 if str2bool(Optional.extract(update_commits)):
937
937
938 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
938 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
939 raise JSONRPCError(
939 raise JSONRPCError(
940 'Operation forbidden because pull request is in state {}, '
940 'Operation forbidden because pull request is in state {}, '
941 'only state {} is allowed.'.format(
941 'only state {} is allowed.'.format(
942 pull_request.pull_request_state, PullRequest.STATE_CREATED))
942 pull_request.pull_request_state, PullRequest.STATE_CREATED))
943
943
944 with pull_request.set_state(PullRequest.STATE_UPDATING):
944 with pull_request.set_state(PullRequest.STATE_UPDATING):
945 if PullRequestModel().has_valid_update_type(pull_request):
945 if PullRequestModel().has_valid_update_type(pull_request):
946 db_user = apiuser.get_instance()
946 db_user = apiuser.get_instance()
947 update_response = PullRequestModel().update_commits(
947 update_response = PullRequestModel().update_commits(
948 pull_request, db_user)
948 pull_request, db_user)
949 commit_changes = update_response.changes or commit_changes
949 commit_changes = update_response.changes or commit_changes
950 Session().commit()
950 Session().commit()
951 commits_changed = True
951 commits_changed = True
952
952
953 # Update reviewers
953 # Update reviewers
954 # serialize and validate passed in given reviewers
954 # serialize and validate passed in given reviewers
955 if reviewer_objects:
955 if reviewer_objects:
956 reviewer_objects = _reviewers_validation(reviewer_objects)
956 reviewer_objects = _reviewers_validation(reviewer_objects)
957
957
958 if observer_objects:
958 if observer_objects:
959 observer_objects = _reviewers_validation(reviewer_objects)
959 observer_objects = _reviewers_validation(reviewer_objects)
960
960
961 # re-use stored rules
961 # re-use stored rules
962 default_reviewers_data = pull_request.reviewer_data
962 default_reviewers_data = pull_request.reviewer_data
963
963
964 __, validate_default_reviewers, validate_observers = \
964 __, validate_default_reviewers, validate_observers = \
965 PullRequestModel().get_reviewer_functions()
965 PullRequestModel().get_reviewer_functions()
966
966
967 if reviewer_objects:
967 if reviewer_objects:
968 try:
968 try:
969 reviewers = validate_default_reviewers(reviewer_objects, default_reviewers_data)
969 reviewers = validate_default_reviewers(reviewer_objects, default_reviewers_data)
970 except ValueError as e:
970 except ValueError as e:
971 raise JSONRPCError('Reviewers Validation: {}'.format(e))
971 raise JSONRPCError('Reviewers Validation: {}'.format(e))
972 else:
972 else:
973 reviewers = []
973 reviewers = []
974
974
975 if observer_objects:
975 if observer_objects:
976 try:
976 try:
977 observers = validate_default_reviewers(reviewer_objects, default_reviewers_data)
977 observers = validate_default_reviewers(reviewer_objects, default_reviewers_data)
978 except ValueError as e:
978 except ValueError as e:
979 raise JSONRPCError('Observer Validation: {}'.format(e))
979 raise JSONRPCError('Observer Validation: {}'.format(e))
980 else:
980 else:
981 observers = []
981 observers = []
982
982
983 reviewers_changed = False
983 reviewers_changed = False
984 reviewers_changes = {"added": [], "removed": []}
984 reviewers_changes = {"added": [], "removed": []}
985 if reviewers:
985 if reviewers:
986 old_calculated_status = pull_request.calculated_review_status()
986 old_calculated_status = pull_request.calculated_review_status()
987 added_reviewers, removed_reviewers = \
987 added_reviewers, removed_reviewers = \
988 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser.get_instance())
988 PullRequestModel().update_reviewers(pull_request, reviewers, apiuser.get_instance())
989
989
990 reviewers_changes['added'] = sorted(
990 reviewers_changes['added'] = sorted(
991 [get_user_or_error(n).username for n in added_reviewers])
991 [get_user_or_error(n).username for n in added_reviewers])
992 reviewers_changes['removed'] = sorted(
992 reviewers_changes['removed'] = sorted(
993 [get_user_or_error(n).username for n in removed_reviewers])
993 [get_user_or_error(n).username for n in removed_reviewers])
994 Session().commit()
994 Session().commit()
995
995
996 # trigger status changed if change in reviewers changes the status
996 # trigger status changed if change in reviewers changes the status
997 calculated_status = pull_request.calculated_review_status()
997 calculated_status = pull_request.calculated_review_status()
998 if old_calculated_status != calculated_status:
998 if old_calculated_status != calculated_status:
999 PullRequestModel().trigger_pull_request_hook(
999 PullRequestModel().trigger_pull_request_hook(
1000 pull_request, apiuser, 'review_status_change',
1000 pull_request, apiuser, 'review_status_change',
1001 data={'status': calculated_status})
1001 data={'status': calculated_status})
1002 reviewers_changed = True
1002 reviewers_changed = True
1003
1003
1004 observers_changed = False
1004 observers_changed = False
1005 observers_changes = {"added": [], "removed": []}
1005 observers_changes = {"added": [], "removed": []}
1006 if observers:
1006 if observers:
1007 added_observers, removed_observers = \
1007 added_observers, removed_observers = \
1008 PullRequestModel().update_observers(pull_request, observers, apiuser.get_instance())
1008 PullRequestModel().update_observers(pull_request, observers, apiuser.get_instance())
1009
1009
1010 observers_changes['added'] = sorted(
1010 observers_changes['added'] = sorted(
1011 [get_user_or_error(n).username for n in added_observers])
1011 [get_user_or_error(n).username for n in added_observers])
1012 observers_changes['removed'] = sorted(
1012 observers_changes['removed'] = sorted(
1013 [get_user_or_error(n).username for n in removed_observers])
1013 [get_user_or_error(n).username for n in removed_observers])
1014 Session().commit()
1014 Session().commit()
1015
1015
1016 reviewers_changed = True
1016 reviewers_changed = True
1017
1017
1018 # push changed to channelstream
1018 # push changed to channelstream
1019 if commits_changed or reviewers_changed or observers_changed:
1019 if commits_changed or reviewers_changed or observers_changed:
1020 pr_broadcast_channel = channelstream.pr_channel(pull_request)
1020 pr_broadcast_channel = channelstream.pr_channel(pull_request)
1021 msg = 'Pull request was updated.'
1021 msg = 'Pull request was updated.'
1022 channelstream.pr_update_channelstream_push(
1022 channelstream.pr_update_channelstream_push(
1023 request, pr_broadcast_channel, apiuser, msg)
1023 request, pr_broadcast_channel, apiuser, msg)
1024
1024
1025 data = {
1025 data = {
1026 'msg': 'Updated pull request `{}`'.format(pull_request.pull_request_id),
1026 'msg': 'Updated pull request `{}`'.format(pull_request.pull_request_id),
1027 'pull_request': pull_request.get_api_data(),
1027 'pull_request': pull_request.get_api_data(),
1028 'updated_commits': commit_changes,
1028 'updated_commits': commit_changes,
1029 'updated_reviewers': reviewers_changes,
1029 'updated_reviewers': reviewers_changes,
1030 'updated_observers': observers_changes,
1030 'updated_observers': observers_changes,
1031 }
1031 }
1032
1032
1033 return data
1033 return data
1034
1034
1035
1035
1036 @jsonrpc_method()
1036 @jsonrpc_method()
1037 def close_pull_request(
1037 def close_pull_request(
1038 request, apiuser, pullrequestid, repoid=Optional(None),
1038 request, apiuser, pullrequestid, repoid=Optional(None),
1039 userid=Optional(OAttr('apiuser')), message=Optional('')):
1039 userid=Optional(OAttr('apiuser')), message=Optional('')):
1040 """
1040 """
1041 Close the pull request specified by `pullrequestid`.
1041 Close the pull request specified by `pullrequestid`.
1042
1042
1043 :param apiuser: This is filled automatically from the |authtoken|.
1043 :param apiuser: This is filled automatically from the |authtoken|.
1044 :type apiuser: AuthUser
1044 :type apiuser: AuthUser
1045 :param repoid: Repository name or repository ID to which the pull
1045 :param repoid: Repository name or repository ID to which the pull
1046 request belongs.
1046 request belongs.
1047 :type repoid: str or int
1047 :type repoid: str or int
1048 :param pullrequestid: ID of the pull request to be closed.
1048 :param pullrequestid: ID of the pull request to be closed.
1049 :type pullrequestid: int
1049 :type pullrequestid: int
1050 :param userid: Close the pull request as this user.
1050 :param userid: Close the pull request as this user.
1051 :type userid: Optional(str or int)
1051 :type userid: Optional(str or int)
1052 :param message: Optional message to close the Pull Request with. If not
1052 :param message: Optional message to close the Pull Request with. If not
1053 specified it will be generated automatically.
1053 specified it will be generated automatically.
1054 :type message: Optional(str)
1054 :type message: Optional(str)
1055
1055
1056 Example output:
1056 Example output:
1057
1057
1058 .. code-block:: bash
1058 .. code-block:: bash
1059
1059
1060 "id": <id_given_in_input>,
1060 "id": <id_given_in_input>,
1061 "result": {
1061 "result": {
1062 "pull_request_id": "<int>",
1062 "pull_request_id": "<int>",
1063 "close_status": "<str:status_lbl>,
1063 "close_status": "<str:status_lbl>,
1064 "closed": "<bool>"
1064 "closed": "<bool>"
1065 },
1065 },
1066 "error": null
1066 "error": null
1067
1067
1068 """
1068 """
1069 _ = request.translate
1069 _ = request.translate
1070
1070
1071 pull_request = get_pull_request_or_error(pullrequestid)
1071 pull_request = get_pull_request_or_error(pullrequestid)
1072 if Optional.extract(repoid):
1072 if Optional.extract(repoid):
1073 repo = get_repo_or_error(repoid)
1073 repo = get_repo_or_error(repoid)
1074 else:
1074 else:
1075 repo = pull_request.target_repo
1075 repo = pull_request.target_repo
1076
1076
1077 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
1077 is_repo_admin = HasRepoPermissionAnyApi('repository.admin')(
1078 user=apiuser, repo_name=repo.repo_name)
1078 user=apiuser, repo_name=repo.repo_name)
1079 if not isinstance(userid, Optional):
1079 if not isinstance(userid, Optional):
1080 if has_superadmin_permission(apiuser) or is_repo_admin:
1080 if has_superadmin_permission(apiuser) or is_repo_admin:
1081 apiuser = get_user_or_error(userid)
1081 apiuser = get_user_or_error(userid)
1082 else:
1082 else:
1083 raise JSONRPCError('userid is not the same as your user')
1083 raise JSONRPCError('userid is not the same as your user')
1084
1084
1085 if pull_request.is_closed():
1085 if pull_request.is_closed():
1086 raise JSONRPCError(
1086 raise JSONRPCError(
1087 'pull request `%s` is already closed' % (pullrequestid,))
1087 'pull request `%s` is already closed' % (pullrequestid,))
1088
1088
1089 # only owner or admin or person with write permissions
1089 # only owner or admin or person with write permissions
1090 allowed_to_close = PullRequestModel().check_user_update(
1090 allowed_to_close = PullRequestModel().check_user_update(
1091 pull_request, apiuser, api=True)
1091 pull_request, apiuser, api=True)
1092
1092
1093 if not allowed_to_close:
1093 if not allowed_to_close:
1094 raise JSONRPCError(
1094 raise JSONRPCError(
1095 'pull request `%s` close failed, no permission to close.' % (
1095 'pull request `%s` close failed, no permission to close.' % (
1096 pullrequestid,))
1096 pullrequestid,))
1097
1097
1098 # message we're using to close the PR, else it's automatically generated
1098 # message we're using to close the PR, else it's automatically generated
1099 message = Optional.extract(message)
1099 message = Optional.extract(message)
1100
1100
1101 # finally close the PR, with proper message comment
1101 # finally close the PR, with proper message comment
1102 comment, status = PullRequestModel().close_pull_request_with_comment(
1102 comment, status = PullRequestModel().close_pull_request_with_comment(
1103 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1103 pull_request, apiuser, repo, message=message, auth_user=apiuser)
1104 status_lbl = ChangesetStatus.get_status_lbl(status)
1104 status_lbl = ChangesetStatus.get_status_lbl(status)
1105
1105
1106 Session().commit()
1106 Session().commit()
1107
1107
1108 data = {
1108 data = {
1109 'pull_request_id': pull_request.pull_request_id,
1109 'pull_request_id': pull_request.pull_request_id,
1110 'close_status': status_lbl,
1110 'close_status': status_lbl,
1111 'closed': True,
1111 'closed': True,
1112 }
1112 }
1113 return data
1113 return data
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
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