##// END OF EJS Templates
tests: fixed simplevcs tests
super-admin -
r5167:039ef6ec default
parent child Browse files
Show More
@@ -1,136 +1,138 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 """
19 """
20 py.test config for test suite for making push/pull operations.
20 py.test config for test suite for making push/pull operations.
21
21
22 .. important::
22 .. important::
23
23
24 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
24 You must have git >= 1.8.5 for tests to work fine. With 68b939b git started
25 to redirect things to stderr instead of stdout.
25 to redirect things to stderr instead of stdout.
26 """
26 """
27
27
28 import pytest
28 import pytest
29 import logging
29 import logging
30
30
31 from rhodecode.authentication import AuthenticationPluginRegistry
31 from rhodecode.authentication import AuthenticationPluginRegistry
32 from rhodecode.model.db import Permission, User
32 from rhodecode.model.db import Permission, User
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34 from rhodecode.model.settings import SettingsModel
34 from rhodecode.model.settings import SettingsModel
35 from rhodecode.model.user import UserModel
35 from rhodecode.model.user import UserModel
36
36
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40 # Docker image running httpbin...
40 # Docker image running httpbin...
41 HTTPBIN_DOMAIN = 'http://httpbin'
41 HTTPBIN_DOMAIN = 'http://httpbin'
42 HTTPBIN_POST = HTTPBIN_DOMAIN + '/post'
42 HTTPBIN_POST = HTTPBIN_DOMAIN + '/post'
43
43
44
44
45 @pytest.fixture()
45 @pytest.fixture()
46 def enable_auth_plugins(request, baseapp, csrf_token):
46 def enable_auth_plugins(request, baseapp, csrf_token):
47 """
47 """
48 Return a factory object that when called, allows to control which
48 Return a factory object that when called, allows to control which
49 authentication plugins are enabled.
49 authentication plugins are enabled.
50 """
50 """
51
51
52 class AuthPluginManager(object):
52 class AuthPluginManager(object):
53
53
54 def cleanup(self):
54 def cleanup(self):
55 self._enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
55 self._enable_plugins(['egg:rhodecode-enterprise-ce#rhodecode'])
56
56
57 def enable(self, plugins_list, override=None):
57 def enable(self, plugins_list, override=None):
58 return self._enable_plugins(plugins_list, override)
58 return self._enable_plugins(plugins_list, override)
59
59
60 def _enable_plugins(self, plugins_list, override=None):
60 def _enable_plugins(self, plugins_list, override=None):
61 override = override or {}
61 override = override or {}
62 params = {
62 params = {
63 'auth_plugins': ','.join(plugins_list),
63 'auth_plugins': ','.join(plugins_list),
64 }
64 }
65
65
66 # helper translate some names to others, to fix settings code
66 # helper translate some names to others, to fix settings code
67 name_map = {
67 name_map = {
68 'token': 'authtoken'
68 'token': 'authtoken'
69 }
69 }
70 log.debug('enable_auth_plugins: enabling following auth-plugins: %s', plugins_list)
70 log.debug('enable_auth_plugins: enabling following auth-plugins: %s', plugins_list)
71
71
72 for module in plugins_list:
72 for module in plugins_list:
73 plugin_name = module.partition('#')[-1]
73 plugin_name = module.partition('#')[-1]
74 if plugin_name in name_map:
74 if plugin_name in name_map:
75 plugin_name = name_map[plugin_name]
75 plugin_name = name_map[plugin_name]
76 enabled_plugin = f'auth_{plugin_name}_enabled'
76 enabled_plugin = f'auth_{plugin_name}_enabled'
77 cache_ttl = f'auth_{plugin_name}_cache_ttl'
77 cache_ttl = f'auth_{plugin_name}_cache_ttl'
78
78
79 # default params that are needed for each plugin,
79 # default params that are needed for each plugin,
80 # `enabled` and `cache_ttl`
80 # `enabled` and `cache_ttl`
81 params.update({
81 params.update({
82 enabled_plugin: True,
82 enabled_plugin: True,
83 cache_ttl: 0
83 cache_ttl: 0
84 })
84 })
85 if override.get:
85 if override.get:
86 params.update(override.get(module, {}))
86 params.update(override.get(module, {}))
87
87
88 validated_params = params
88 validated_params = params
89
89
90 for k, v in validated_params.items():
90 for k, v in validated_params.items():
91 setting = SettingsModel().create_or_update_setting(k, v)
91 setting = SettingsModel().create_or_update_setting(k, v)
92 Session().add(setting)
92 Session().add(setting)
93 Session().commit()
93 Session().commit()
94
94
95 AuthenticationPluginRegistry.invalidate_auth_plugins_cache(hard=True)
95 AuthenticationPluginRegistry.invalidate_auth_plugins_cache(hard=True)
96
96
97 enabled_plugins = SettingsModel().get_auth_plugins()
97 enabled_plugins = SettingsModel().get_auth_plugins()
98 assert plugins_list == enabled_plugins
98 assert plugins_list == enabled_plugins
99
99
100 enabler = AuthPluginManager()
100 enabler = AuthPluginManager()
101 request.addfinalizer(enabler.cleanup)
101 request.addfinalizer(enabler.cleanup)
102
102
103 return enabler
103 return enabler
104
104
105
105
106 @pytest.fixture()
106 @pytest.fixture()
107 def test_user_factory(request, baseapp):
107 def test_user_factory(request, baseapp):
108
108
109 def user_factory(username='test_user', password='qweqwe', first_name='John', last_name='Testing', **kwargs):
109 def user_factory(username='test_user', password='qweqwe', first_name='John', last_name='Testing', **kwargs):
110 usr = UserModel().create_or_update(
110 usr = UserModel().create_or_update(
111 username=username,
111 username=username,
112 password=password,
112 password=password,
113 email=f'{username}@rhodecode.org',
113 email=f'{username}@rhodecode.org',
114 firstname=first_name, lastname=last_name)
114 firstname=first_name, lastname=last_name)
115 Session().commit()
115 Session().commit()
116
116
117 for k, v in kwargs.items():
117 for k, v in kwargs.items():
118 setattr(usr, k, v)
118 setattr(usr, k, v)
119 Session().add(usr)
119 Session().add(usr)
120
120
121 assert User.get_by_username(username) == usr
121 new_usr = User.get_by_username(username)
122 new_usr_id = new_usr.user_id
123 assert new_usr == usr
122
124
123 @request.addfinalizer
125 @request.addfinalizer
124 def cleanup():
126 def cleanup():
125 if UserModel().get_user(usr.user_id) is None:
127 if User.get(new_usr_id) is None:
126 return
128 return
127
129
128 perm = Permission.query().all()
130 perm = Permission.query().all()
129 for p in perm:
131 for p in perm:
130 UserModel().revoke_perm(usr, p)
132 UserModel().revoke_perm(usr, p)
131
133
132 UserModel().delete(usr.user_id)
134 UserModel().delete(new_usr_id)
133 Session().commit()
135 Session().commit()
134 return usr
136 return usr
135
137
136 return user_factory
138 return user_factory
@@ -1,492 +1,487 b''
1
1
2 # Copyright (C) 2010-2023 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.str_utils import base64_to_str
23 from rhodecode.lib.str_utils import base64_to_str
24 from rhodecode.lib.utils2 import AttributeDict
24 from rhodecode.lib.utils2 import AttributeDict
25 from rhodecode.tests.utils import CustomTestApp
25 from rhodecode.tests.utils import CustomTestApp
26
26
27 from rhodecode.lib.caching_query import FromCache
27 from rhodecode.lib.caching_query import FromCache
28 from rhodecode.lib.hooks_daemon import DummyHooksCallbackDaemon
28 from rhodecode.lib.hooks_daemon import DummyHooksCallbackDaemon
29 from rhodecode.lib.middleware import simplevcs
29 from rhodecode.lib.middleware import simplevcs
30 from rhodecode.lib.middleware.https_fixup import HttpsFixup
30 from rhodecode.lib.middleware.https_fixup import HttpsFixup
31 from rhodecode.lib.middleware.utils import scm_app_http
31 from rhodecode.lib.middleware.utils import scm_app_http
32 from rhodecode.model.db import User, _hash_key
32 from rhodecode.model.db import User, _hash_key
33 from rhodecode.model.meta import Session, cache as db_cache
33 from rhodecode.model.meta import Session, cache as db_cache
34 from rhodecode.tests import (
34 from rhodecode.tests import (
35 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
35 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
36 from rhodecode.tests.lib.middleware import mock_scm_app
36 from rhodecode.tests.lib.middleware import mock_scm_app
37
37
38
38
39 class StubVCSController(simplevcs.SimpleVCS):
39 class StubVCSController(simplevcs.SimpleVCS):
40
40
41 SCM = 'hg'
41 SCM = 'hg'
42 stub_response_body = tuple()
42 stub_response_body = tuple()
43
43
44 def __init__(self, *args, **kwargs):
44 def __init__(self, *args, **kwargs):
45 super(StubVCSController, self).__init__(*args, **kwargs)
45 super(StubVCSController, self).__init__(*args, **kwargs)
46 self._action = 'pull'
46 self._action = 'pull'
47 self._is_shadow_repo_dir = True
47 self._is_shadow_repo_dir = True
48 self._name = HG_REPO
48 self._name = HG_REPO
49 self.set_repo_names(None)
49 self.set_repo_names(None)
50
50
51 @property
51 @property
52 def is_shadow_repo_dir(self):
52 def is_shadow_repo_dir(self):
53 return self._is_shadow_repo_dir
53 return self._is_shadow_repo_dir
54
54
55 def _get_repository_name(self, environ):
55 def _get_repository_name(self, environ):
56 return self._name
56 return self._name
57
57
58 def _get_action(self, environ):
58 def _get_action(self, environ):
59 return self._action
59 return self._action
60
60
61 def _create_wsgi_app(self, repo_path, repo_name, config):
61 def _create_wsgi_app(self, repo_path, repo_name, config):
62 def fake_app(environ, start_response):
62 def fake_app(environ, start_response):
63 headers = [
63 headers = [
64 ('Http-Accept', 'application/mercurial')
64 ('Http-Accept', 'application/mercurial')
65 ]
65 ]
66 start_response('200 OK', headers)
66 start_response('200 OK', headers)
67 return self.stub_response_body
67 return self.stub_response_body
68 return fake_app
68 return fake_app
69
69
70 def _create_config(self, extras, repo_name, scheme='http'):
70 def _create_config(self, extras, repo_name, scheme='http'):
71 return None
71 return None
72
72
73
73
74 @pytest.fixture()
74 @pytest.fixture()
75 def vcscontroller(baseapp, config_stub, request_stub):
75 def vcscontroller(baseapp, config_stub, request_stub):
76 from rhodecode.config.middleware import ce_auth_resources
76 from rhodecode.config.middleware import ce_auth_resources
77
77
78 config_stub.testing_securitypolicy()
78 config_stub.testing_securitypolicy()
79 config_stub.include('rhodecode.authentication')
79 config_stub.include('rhodecode.authentication')
80
80
81 for resource in ce_auth_resources:
81 for resource in ce_auth_resources:
82 config_stub.include(resource)
82 config_stub.include(resource)
83
83
84 controller = StubVCSController(
84 controller = StubVCSController(
85 baseapp.config.get_settings(), request_stub.registry)
85 baseapp.config.get_settings(), request_stub.registry)
86 app = HttpsFixup(controller, baseapp.config.get_settings())
86 app = HttpsFixup(controller, baseapp.config.get_settings())
87 app = CustomTestApp(app)
87 app = CustomTestApp(app)
88
88
89 _remove_default_user_from_query_cache()
89 _remove_default_user_from_query_cache()
90
90
91 # Sanity checks that things are set up correctly
91 # Sanity checks that things are set up correctly
92 app.get('/' + HG_REPO, status=200)
92 app.get('/' + HG_REPO, status=200)
93
93
94 app.controller = controller
94 app.controller = controller
95 return app
95 return app
96
96
97
97
98 def _remove_default_user_from_query_cache():
98 def _remove_default_user_from_query_cache():
99 user = User.get_default_user(cache=True)
99 user = User.get_default_user(cache=True)
100 query = Session().query(User).filter(User.username == user.username)
100 query = Session().query(User).filter(User.username == user.username)
101 query = query.options(
101 query = query.options(
102 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
102 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
103
103
104 db_cache.invalidate(
104 db_cache.invalidate(
105 query, {},
105 query, {},
106 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
106 FromCache("sql_cache_short", f"get_user_{_hash_key(user.username)}"))
107
107
108 Session().expire(user)
108 Session().expire(user)
109
109
110
110
111 def test_handles_exceptions_during_permissions_checks(
111 def test_handles_exceptions_during_permissions_checks(
112 vcscontroller, disable_anonymous_user, enable_auth_plugins, test_user_factory):
112 vcscontroller, disable_anonymous_user, enable_auth_plugins, test_user_factory):
113
113
114 test_password = 'qweqwe'
114 test_password = 'qweqwe'
115 test_user = test_user_factory(password=test_password, extern_type='headers', extern_name='headers')
115 test_user = test_user_factory(password=test_password, extern_type='headers', extern_name='headers')
116 test_username = test_user.username
116 test_username = test_user.username
117
117
118 enable_auth_plugins.enable([
118 enable_auth_plugins.enable([
119 'egg:rhodecode-enterprise-ce#headers',
119 'egg:rhodecode-enterprise-ce#headers',
120 'egg:rhodecode-enterprise-ce#token',
120 'egg:rhodecode-enterprise-ce#token',
121 'egg:rhodecode-enterprise-ce#rhodecode'],
121 'egg:rhodecode-enterprise-ce#rhodecode'],
122 override={
122 override={
123 'egg:rhodecode-enterprise-ce#headers': {'auth_headers_header': 'REMOTE_USER'}
123 'egg:rhodecode-enterprise-ce#headers': {'auth_headers_header': 'REMOTE_USER'}
124 })
124 })
125
125
126 user_and_pass = f'{test_username}:{test_password}'
126 user_and_pass = f'{test_username}:{test_password}'
127 auth_password = base64_to_str(user_and_pass)
127 auth_password = base64_to_str(user_and_pass)
128
128
129 extra_environ = {
129 extra_environ = {
130 'AUTH_TYPE': 'Basic',
130 'AUTH_TYPE': 'Basic',
131 'HTTP_AUTHORIZATION': f'Basic {auth_password}',
131 'HTTP_AUTHORIZATION': f'Basic {auth_password}',
132 'REMOTE_USER': test_username,
132 'REMOTE_USER': test_username,
133 }
133 }
134
134
135 # Verify that things are hooked up correctly, we pass user with headers bound auth, and headers filled in
135 # Verify that things are hooked up correctly, we pass user with headers bound auth, and headers filled in
136 vcscontroller.get('/', status=200, extra_environ=extra_environ)
136 vcscontroller.get('/', status=200, extra_environ=extra_environ)
137
137
138 # Simulate trouble during permission checks
138 # Simulate trouble during permission checks
139 with mock.patch('rhodecode.model.db.User.get_by_username',
139 with mock.patch('rhodecode.model.db.User.get_by_username',
140 side_effect=Exception('permission_error_test')) as get_user:
140 side_effect=Exception('permission_error_test')) as get_user:
141 # Verify that a correct 500 is returned and check that the expected
141 # Verify that a correct 500 is returned and check that the expected
142 # code path was hit.
142 # code path was hit.
143 vcscontroller.get('/', status=500, extra_environ=extra_environ)
143 vcscontroller.get('/', status=500, extra_environ=extra_environ)
144 assert get_user.called
144 assert get_user.called
145
145
146
146
147 def test_returns_forbidden_if_no_anonymous_access(
148 vcscontroller, disable_anonymous_user):
149 vcscontroller.get('/', status=401)
150
151
152 class StubFailVCSController(simplevcs.SimpleVCS):
147 class StubFailVCSController(simplevcs.SimpleVCS):
153 def _handle_request(self, environ, start_response):
148 def _handle_request(self, environ, start_response):
154 raise Exception("BOOM")
149 raise Exception("BOOM")
155
150
156
151
157 @pytest.fixture(scope='module')
152 @pytest.fixture(scope='module')
158 def fail_controller(baseapp):
153 def fail_controller(baseapp):
159 controller = StubFailVCSController(
154 controller = StubFailVCSController(
160 baseapp.config.get_settings(), baseapp.config)
155 baseapp.config.get_settings(), baseapp.config)
161 controller = HttpsFixup(controller, baseapp.config.get_settings())
156 controller = HttpsFixup(controller, baseapp.config.get_settings())
162 controller = CustomTestApp(controller)
157 controller = CustomTestApp(controller)
163 return controller
158 return controller
164
159
165
160
166 def test_handles_exceptions_as_internal_server_error(fail_controller):
161 def test_handles_exceptions_as_internal_server_error(fail_controller):
167 fail_controller.get('/', status=500)
162 fail_controller.get('/', status=500)
168
163
169
164
170 def test_provides_traceback_for_appenlight(fail_controller):
165 def test_provides_traceback_for_appenlight(fail_controller):
171 response = fail_controller.get(
166 response = fail_controller.get(
172 '/', status=500, extra_environ={'appenlight.client': 'fake'})
167 '/', status=500, extra_environ={'appenlight.client': 'fake'})
173 assert 'appenlight.__traceback' in response.request.environ
168 assert 'appenlight.__traceback' in response.request.environ
174
169
175
170
176 def test_provides_utils_scm_app_as_scm_app_by_default(baseapp, request_stub):
171 def test_provides_utils_scm_app_as_scm_app_by_default(baseapp, request_stub):
177 controller = StubVCSController(baseapp.config.get_settings(), request_stub.registry)
172 controller = StubVCSController(baseapp.config.get_settings(), request_stub.registry)
178 assert controller.scm_app is scm_app_http
173 assert controller.scm_app is scm_app_http
179
174
180
175
181 def test_allows_to_override_scm_app_via_config(baseapp, request_stub):
176 def test_allows_to_override_scm_app_via_config(baseapp, request_stub):
182 config = baseapp.config.get_settings().copy()
177 config = baseapp.config.get_settings().copy()
183 config['vcs.scm_app_implementation'] = (
178 config['vcs.scm_app_implementation'] = (
184 'rhodecode.tests.lib.middleware.mock_scm_app')
179 'rhodecode.tests.lib.middleware.mock_scm_app')
185 controller = StubVCSController(config, request_stub.registry)
180 controller = StubVCSController(config, request_stub.registry)
186 assert controller.scm_app is mock_scm_app
181 assert controller.scm_app is mock_scm_app
187
182
188
183
189 @pytest.mark.parametrize('query_string, expected', [
184 @pytest.mark.parametrize('query_string, expected', [
190 ('cmd=stub_command', True),
185 ('cmd=stub_command', True),
191 ('cmd=listkeys', False),
186 ('cmd=listkeys', False),
192 ])
187 ])
193 def test_should_check_locking(query_string, expected):
188 def test_should_check_locking(query_string, expected):
194 result = simplevcs._should_check_locking(query_string)
189 result = simplevcs._should_check_locking(query_string)
195 assert result == expected
190 assert result == expected
196
191
197
192
198 class TestShadowRepoRegularExpression(object):
193 class TestShadowRepoRegularExpression(object):
199 pr_segment = 'pull-request'
194 pr_segment = 'pull-request'
200 shadow_segment = 'repository'
195 shadow_segment = 'repository'
201
196
202 @pytest.mark.parametrize('url, expected', [
197 @pytest.mark.parametrize('url, expected', [
203 # repo with/without groups
198 # repo with/without groups
204 ('My-Repo/{pr_segment}/1/{shadow_segment}', True),
199 ('My-Repo/{pr_segment}/1/{shadow_segment}', True),
205 ('Group/My-Repo/{pr_segment}/2/{shadow_segment}', True),
200 ('Group/My-Repo/{pr_segment}/2/{shadow_segment}', True),
206 ('Group/Sub-Group/My-Repo/{pr_segment}/3/{shadow_segment}', True),
201 ('Group/Sub-Group/My-Repo/{pr_segment}/3/{shadow_segment}', True),
207 ('Group/Sub-Group1/Sub-Group2/My-Repo/{pr_segment}/3/{shadow_segment}', True),
202 ('Group/Sub-Group1/Sub-Group2/My-Repo/{pr_segment}/3/{shadow_segment}', True),
208
203
209 # pull request ID
204 # pull request ID
210 ('MyRepo/{pr_segment}/1/{shadow_segment}', True),
205 ('MyRepo/{pr_segment}/1/{shadow_segment}', True),
211 ('MyRepo/{pr_segment}/1234567890/{shadow_segment}', True),
206 ('MyRepo/{pr_segment}/1234567890/{shadow_segment}', True),
212 ('MyRepo/{pr_segment}/-1/{shadow_segment}', False),
207 ('MyRepo/{pr_segment}/-1/{shadow_segment}', False),
213 ('MyRepo/{pr_segment}/invalid/{shadow_segment}', False),
208 ('MyRepo/{pr_segment}/invalid/{shadow_segment}', False),
214
209
215 # unicode
210 # unicode
216 (u'Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
211 (u'Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
217 (u'Sp€çîál-Gröüp/Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
212 (u'Sp€çîál-Gröüp/Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
218
213
219 # trailing/leading slash
214 # trailing/leading slash
220 ('/My-Repo/{pr_segment}/1/{shadow_segment}', False),
215 ('/My-Repo/{pr_segment}/1/{shadow_segment}', False),
221 ('My-Repo/{pr_segment}/1/{shadow_segment}/', False),
216 ('My-Repo/{pr_segment}/1/{shadow_segment}/', False),
222 ('/My-Repo/{pr_segment}/1/{shadow_segment}/', False),
217 ('/My-Repo/{pr_segment}/1/{shadow_segment}/', False),
223
218
224 # misc
219 # misc
225 ('My-Repo/{pr_segment}/1/{shadow_segment}/extra', False),
220 ('My-Repo/{pr_segment}/1/{shadow_segment}/extra', False),
226 ('My-Repo/{pr_segment}/1/{shadow_segment}extra', False),
221 ('My-Repo/{pr_segment}/1/{shadow_segment}extra', False),
227 ])
222 ])
228 def test_shadow_repo_regular_expression(self, url, expected):
223 def test_shadow_repo_regular_expression(self, url, expected):
229 from rhodecode.lib.middleware.simplevcs import SimpleVCS
224 from rhodecode.lib.middleware.simplevcs import SimpleVCS
230 url = url.format(
225 url = url.format(
231 pr_segment=self.pr_segment,
226 pr_segment=self.pr_segment,
232 shadow_segment=self.shadow_segment)
227 shadow_segment=self.shadow_segment)
233 match_obj = SimpleVCS.shadow_repo_re.match(url)
228 match_obj = SimpleVCS.shadow_repo_re.match(url)
234 assert (match_obj is not None) == expected
229 assert (match_obj is not None) == expected
235
230
236
231
237 @pytest.mark.backends('git', 'hg')
232 @pytest.mark.backends('git', 'hg')
238 class TestShadowRepoExposure(object):
233 class TestShadowRepoExposure(object):
239
234
240 def test_pull_on_shadow_repo_propagates_to_wsgi_app(
235 def test_pull_on_shadow_repo_propagates_to_wsgi_app(
241 self, baseapp, request_stub):
236 self, baseapp, request_stub):
242 """
237 """
243 Check that a pull action to a shadow repo is propagated to the
238 Check that a pull action to a shadow repo is propagated to the
244 underlying wsgi app.
239 underlying wsgi app.
245 """
240 """
246 controller = StubVCSController(
241 controller = StubVCSController(
247 baseapp.config.get_settings(), request_stub.registry)
242 baseapp.config.get_settings(), request_stub.registry)
248 controller._check_ssl = mock.Mock()
243 controller._check_ssl = mock.Mock()
249 controller.is_shadow_repo = True
244 controller.is_shadow_repo = True
250 controller._action = 'pull'
245 controller._action = 'pull'
251 controller._is_shadow_repo_dir = True
246 controller._is_shadow_repo_dir = True
252 controller.stub_response_body = (b'dummy body value',)
247 controller.stub_response_body = (b'dummy body value',)
253 controller._get_default_cache_ttl = mock.Mock(
248 controller._get_default_cache_ttl = mock.Mock(
254 return_value=(False, 0))
249 return_value=(False, 0))
255
250
256 environ_stub = {
251 environ_stub = {
257 'HTTP_HOST': 'test.example.com',
252 'HTTP_HOST': 'test.example.com',
258 'HTTP_ACCEPT': 'application/mercurial',
253 'HTTP_ACCEPT': 'application/mercurial',
259 'REQUEST_METHOD': 'GET',
254 'REQUEST_METHOD': 'GET',
260 'wsgi.url_scheme': 'http',
255 'wsgi.url_scheme': 'http',
261 }
256 }
262
257
263 response = controller(environ_stub, mock.Mock())
258 response = controller(environ_stub, mock.Mock())
264 response_body = b''.join(response)
259 response_body = b''.join(response)
265
260
266 # Assert that we got the response from the wsgi app.
261 # Assert that we got the response from the wsgi app.
267 assert response_body == b''.join(controller.stub_response_body)
262 assert response_body == b''.join(controller.stub_response_body)
268
263
269 def test_pull_on_shadow_repo_that_is_missing(self, baseapp, request_stub):
264 def test_pull_on_shadow_repo_that_is_missing(self, baseapp, request_stub):
270 """
265 """
271 Check that a pull action to a shadow repo is propagated to the
266 Check that a pull action to a shadow repo is propagated to the
272 underlying wsgi app.
267 underlying wsgi app.
273 """
268 """
274 controller = StubVCSController(
269 controller = StubVCSController(
275 baseapp.config.get_settings(), request_stub.registry)
270 baseapp.config.get_settings(), request_stub.registry)
276 controller._check_ssl = mock.Mock()
271 controller._check_ssl = mock.Mock()
277 controller.is_shadow_repo = True
272 controller.is_shadow_repo = True
278 controller._action = 'pull'
273 controller._action = 'pull'
279 controller._is_shadow_repo_dir = False
274 controller._is_shadow_repo_dir = False
280 controller.stub_response_body = (b'dummy body value',)
275 controller.stub_response_body = (b'dummy body value',)
281 environ_stub = {
276 environ_stub = {
282 'HTTP_HOST': 'test.example.com',
277 'HTTP_HOST': 'test.example.com',
283 'HTTP_ACCEPT': 'application/mercurial',
278 'HTTP_ACCEPT': 'application/mercurial',
284 'REQUEST_METHOD': 'GET',
279 'REQUEST_METHOD': 'GET',
285 'wsgi.url_scheme': 'http',
280 'wsgi.url_scheme': 'http',
286 }
281 }
287
282
288 response = controller(environ_stub, mock.Mock())
283 response = controller(environ_stub, mock.Mock())
289 response_body = b''.join(response)
284 response_body = b''.join(response)
290
285
291 # Assert that we got the response from the wsgi app.
286 # Assert that we got the response from the wsgi app.
292 assert b'404 Not Found' in response_body
287 assert b'404 Not Found' in response_body
293
288
294 def test_push_on_shadow_repo_raises(self, baseapp, request_stub):
289 def test_push_on_shadow_repo_raises(self, baseapp, request_stub):
295 """
290 """
296 Check that a push action to a shadow repo is aborted.
291 Check that a push action to a shadow repo is aborted.
297 """
292 """
298 controller = StubVCSController(
293 controller = StubVCSController(
299 baseapp.config.get_settings(), request_stub.registry)
294 baseapp.config.get_settings(), request_stub.registry)
300 controller._check_ssl = mock.Mock()
295 controller._check_ssl = mock.Mock()
301 controller.is_shadow_repo = True
296 controller.is_shadow_repo = True
302 controller._action = 'push'
297 controller._action = 'push'
303 controller.stub_response_body = (b'dummy body value',)
298 controller.stub_response_body = (b'dummy body value',)
304 environ_stub = {
299 environ_stub = {
305 'HTTP_HOST': 'test.example.com',
300 'HTTP_HOST': 'test.example.com',
306 'HTTP_ACCEPT': 'application/mercurial',
301 'HTTP_ACCEPT': 'application/mercurial',
307 'REQUEST_METHOD': 'GET',
302 'REQUEST_METHOD': 'GET',
308 'wsgi.url_scheme': 'http',
303 'wsgi.url_scheme': 'http',
309 }
304 }
310
305
311 response = controller(environ_stub, mock.Mock())
306 response = controller(environ_stub, mock.Mock())
312 response_body = b''.join(response)
307 response_body = b''.join(response)
313
308
314 assert response_body != controller.stub_response_body
309 assert response_body != controller.stub_response_body
315 # Assert that a 406 error is returned.
310 # Assert that a 406 error is returned.
316 assert b'406 Not Acceptable' in response_body
311 assert b'406 Not Acceptable' in response_body
317
312
318 def test_set_repo_names_no_shadow(self, baseapp, request_stub):
313 def test_set_repo_names_no_shadow(self, baseapp, request_stub):
319 """
314 """
320 Check that the set_repo_names method sets all names to the one returned
315 Check that the set_repo_names method sets all names to the one returned
321 by the _get_repository_name method on a request to a non shadow repo.
316 by the _get_repository_name method on a request to a non shadow repo.
322 """
317 """
323 environ_stub = {}
318 environ_stub = {}
324 controller = StubVCSController(
319 controller = StubVCSController(
325 baseapp.config.get_settings(), request_stub.registry)
320 baseapp.config.get_settings(), request_stub.registry)
326 controller._name = 'RepoGroup/MyRepo'
321 controller._name = 'RepoGroup/MyRepo'
327 controller.set_repo_names(environ_stub)
322 controller.set_repo_names(environ_stub)
328 assert not controller.is_shadow_repo
323 assert not controller.is_shadow_repo
329 assert (controller.url_repo_name ==
324 assert (controller.url_repo_name ==
330 controller.acl_repo_name ==
325 controller.acl_repo_name ==
331 controller.vcs_repo_name ==
326 controller.vcs_repo_name ==
332 controller._get_repository_name(environ_stub))
327 controller._get_repository_name(environ_stub))
333
328
334 def test_set_repo_names_with_shadow(
329 def test_set_repo_names_with_shadow(
335 self, baseapp, pr_util, config_stub, request_stub):
330 self, baseapp, pr_util, config_stub, request_stub):
336 """
331 """
337 Check that the set_repo_names method sets correct names on a request
332 Check that the set_repo_names method sets correct names on a request
338 to a shadow repo.
333 to a shadow repo.
339 """
334 """
340 from rhodecode.model.pull_request import PullRequestModel
335 from rhodecode.model.pull_request import PullRequestModel
341
336
342 pull_request = pr_util.create_pull_request()
337 pull_request = pr_util.create_pull_request()
343 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
338 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
344 target=pull_request.target_repo.repo_name,
339 target=pull_request.target_repo.repo_name,
345 pr_id=pull_request.pull_request_id,
340 pr_id=pull_request.pull_request_id,
346 pr_segment=TestShadowRepoRegularExpression.pr_segment,
341 pr_segment=TestShadowRepoRegularExpression.pr_segment,
347 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
342 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
348 controller = StubVCSController(
343 controller = StubVCSController(
349 baseapp.config.get_settings(), request_stub.registry)
344 baseapp.config.get_settings(), request_stub.registry)
350 controller._name = shadow_url
345 controller._name = shadow_url
351 controller.set_repo_names({})
346 controller.set_repo_names({})
352
347
353 # Get file system path to shadow repo for assertions.
348 # Get file system path to shadow repo for assertions.
354 workspace_id = PullRequestModel()._workspace_id(pull_request)
349 workspace_id = PullRequestModel()._workspace_id(pull_request)
355 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
350 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
356
351
357 assert controller.vcs_repo_name == vcs_repo_name
352 assert controller.vcs_repo_name == vcs_repo_name
358 assert controller.url_repo_name == shadow_url
353 assert controller.url_repo_name == shadow_url
359 assert controller.acl_repo_name == pull_request.target_repo.repo_name
354 assert controller.acl_repo_name == pull_request.target_repo.repo_name
360 assert controller.is_shadow_repo
355 assert controller.is_shadow_repo
361
356
362 def test_set_repo_names_with_shadow_but_missing_pr(
357 def test_set_repo_names_with_shadow_but_missing_pr(
363 self, baseapp, pr_util, config_stub, request_stub):
358 self, baseapp, pr_util, config_stub, request_stub):
364 """
359 """
365 Checks that the set_repo_names method enforces matching target repos
360 Checks that the set_repo_names method enforces matching target repos
366 and pull request IDs.
361 and pull request IDs.
367 """
362 """
368 pull_request = pr_util.create_pull_request()
363 pull_request = pr_util.create_pull_request()
369 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
364 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
370 target=pull_request.target_repo.repo_name,
365 target=pull_request.target_repo.repo_name,
371 pr_id=999999999,
366 pr_id=999999999,
372 pr_segment=TestShadowRepoRegularExpression.pr_segment,
367 pr_segment=TestShadowRepoRegularExpression.pr_segment,
373 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
368 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
374 controller = StubVCSController(
369 controller = StubVCSController(
375 baseapp.config.get_settings(), request_stub.registry)
370 baseapp.config.get_settings(), request_stub.registry)
376 controller._name = shadow_url
371 controller._name = shadow_url
377 controller.set_repo_names({})
372 controller.set_repo_names({})
378
373
379 assert not controller.is_shadow_repo
374 assert not controller.is_shadow_repo
380 assert (controller.url_repo_name ==
375 assert (controller.url_repo_name ==
381 controller.acl_repo_name ==
376 controller.acl_repo_name ==
382 controller.vcs_repo_name)
377 controller.vcs_repo_name)
383
378
384
379
385 @pytest.mark.usefixtures('baseapp')
380 @pytest.mark.usefixtures('baseapp')
386 class TestGenerateVcsResponse(object):
381 class TestGenerateVcsResponse(object):
387
382
388 def test_ensures_that_start_response_is_called_early_enough(self):
383 def test_ensures_that_start_response_is_called_early_enough(self):
389 self.call_controller_with_response_body(iter(['a', 'b']))
384 self.call_controller_with_response_body(iter(['a', 'b']))
390 assert self.start_response.called
385 assert self.start_response.called
391
386
392 def test_invalidates_cache_after_body_is_consumed(self):
387 def test_invalidates_cache_after_body_is_consumed(self):
393 result = self.call_controller_with_response_body(iter(['a', 'b']))
388 result = self.call_controller_with_response_body(iter(['a', 'b']))
394 assert not self.was_cache_invalidated()
389 assert not self.was_cache_invalidated()
395 # Consume the result
390 # Consume the result
396 list(result)
391 list(result)
397 assert self.was_cache_invalidated()
392 assert self.was_cache_invalidated()
398
393
399 def test_raises_unknown_exceptions(self):
394 def test_raises_unknown_exceptions(self):
400 result = self.call_controller_with_response_body(
395 result = self.call_controller_with_response_body(
401 self.raise_result_iter(vcs_kind='unknown'))
396 self.raise_result_iter(vcs_kind='unknown'))
402 with pytest.raises(Exception):
397 with pytest.raises(Exception):
403 list(result)
398 list(result)
404
399
405 def test_prepare_callback_daemon_is_called(self):
400 def test_prepare_callback_daemon_is_called(self):
406 def side_effect(extras, environ, action, txn_id=None):
401 def side_effect(extras, environ, action, txn_id=None):
407 return DummyHooksCallbackDaemon(), extras
402 return DummyHooksCallbackDaemon(), extras
408
403
409 prepare_patcher = mock.patch.object(
404 prepare_patcher = mock.patch.object(
410 StubVCSController, '_prepare_callback_daemon')
405 StubVCSController, '_prepare_callback_daemon')
411 with prepare_patcher as prepare_mock:
406 with prepare_patcher as prepare_mock:
412 prepare_mock.side_effect = side_effect
407 prepare_mock.side_effect = side_effect
413 self.call_controller_with_response_body(iter(['a', 'b']))
408 self.call_controller_with_response_body(iter(['a', 'b']))
414 assert prepare_mock.called
409 assert prepare_mock.called
415 assert prepare_mock.call_count == 1
410 assert prepare_mock.call_count == 1
416
411
417 def call_controller_with_response_body(self, response_body):
412 def call_controller_with_response_body(self, response_body):
418 settings = {
413 settings = {
419 'base_path': 'fake_base_path',
414 'base_path': 'fake_base_path',
420 'vcs.hooks.protocol': 'http',
415 'vcs.hooks.protocol': 'http',
421 'vcs.hooks.direct_calls': False,
416 'vcs.hooks.direct_calls': False,
422 }
417 }
423 registry = AttributeDict()
418 registry = AttributeDict()
424 controller = StubVCSController(settings, registry)
419 controller = StubVCSController(settings, registry)
425 controller._invalidate_cache = mock.Mock()
420 controller._invalidate_cache = mock.Mock()
426 controller.stub_response_body = response_body
421 controller.stub_response_body = response_body
427 self.start_response = mock.Mock()
422 self.start_response = mock.Mock()
428 result = controller._generate_vcs_response(
423 result = controller._generate_vcs_response(
429 environ={}, start_response=self.start_response,
424 environ={}, start_response=self.start_response,
430 repo_path='fake_repo_path',
425 repo_path='fake_repo_path',
431 extras={}, action='push')
426 extras={}, action='push')
432 self.controller = controller
427 self.controller = controller
433 return result
428 return result
434
429
435 def raise_result_iter(self, vcs_kind='repo_locked'):
430 def raise_result_iter(self, vcs_kind='repo_locked'):
436 """
431 """
437 Simulates an exception due to a vcs raised exception if kind vcs_kind
432 Simulates an exception due to a vcs raised exception if kind vcs_kind
438 """
433 """
439 raise self.vcs_exception(vcs_kind=vcs_kind)
434 raise self.vcs_exception(vcs_kind=vcs_kind)
440 yield "never_reached"
435 yield "never_reached"
441
436
442 def vcs_exception(self, vcs_kind='repo_locked'):
437 def vcs_exception(self, vcs_kind='repo_locked'):
443 locked_exception = Exception('TEST_MESSAGE')
438 locked_exception = Exception('TEST_MESSAGE')
444 locked_exception._vcs_kind = vcs_kind
439 locked_exception._vcs_kind = vcs_kind
445 return locked_exception
440 return locked_exception
446
441
447 def was_cache_invalidated(self):
442 def was_cache_invalidated(self):
448 return self.controller._invalidate_cache.called
443 return self.controller._invalidate_cache.called
449
444
450
445
451 class TestInitializeGenerator(object):
446 class TestInitializeGenerator(object):
452
447
453 def test_drains_first_element(self):
448 def test_drains_first_element(self):
454 gen = self.factory(['__init__', 1, 2])
449 gen = self.factory(['__init__', 1, 2])
455 result = list(gen)
450 result = list(gen)
456 assert result == [1, 2]
451 assert result == [1, 2]
457
452
458 @pytest.mark.parametrize('values', [
453 @pytest.mark.parametrize('values', [
459 [],
454 [],
460 [1, 2],
455 [1, 2],
461 ])
456 ])
462 def test_raises_value_error(self, values):
457 def test_raises_value_error(self, values):
463 with pytest.raises(ValueError):
458 with pytest.raises(ValueError):
464 self.factory(values)
459 self.factory(values)
465
460
466 @simplevcs.initialize_generator
461 @simplevcs.initialize_generator
467 def factory(self, iterable):
462 def factory(self, iterable):
468 for elem in iterable:
463 for elem in iterable:
469 yield elem
464 yield elem
470
465
471
466
472 class TestPrepareHooksDaemon(object):
467 class TestPrepareHooksDaemon(object):
473 def test_calls_imported_prepare_callback_daemon(self, app_settings, request_stub):
468 def test_calls_imported_prepare_callback_daemon(self, app_settings, request_stub):
474 expected_extras = {'extra1': 'value1'}
469 expected_extras = {'extra1': 'value1'}
475 daemon = DummyHooksCallbackDaemon()
470 daemon = DummyHooksCallbackDaemon()
476
471
477 controller = StubVCSController(app_settings, request_stub.registry)
472 controller = StubVCSController(app_settings, request_stub.registry)
478 prepare_patcher = mock.patch.object(
473 prepare_patcher = mock.patch.object(
479 simplevcs, 'prepare_callback_daemon',
474 simplevcs, 'prepare_callback_daemon',
480 return_value=(daemon, expected_extras))
475 return_value=(daemon, expected_extras))
481 with prepare_patcher as prepare_mock:
476 with prepare_patcher as prepare_mock:
482 callback_daemon, extras = controller._prepare_callback_daemon(
477 callback_daemon, extras = controller._prepare_callback_daemon(
483 expected_extras.copy(), {}, 'push')
478 expected_extras.copy(), {}, 'push')
484 prepare_mock.assert_called_once_with(
479 prepare_mock.assert_called_once_with(
485 expected_extras,
480 expected_extras,
486 protocol=app_settings['vcs.hooks.protocol'],
481 protocol=app_settings['vcs.hooks.protocol'],
487 host=app_settings['vcs.hooks.host'],
482 host=app_settings['vcs.hooks.host'],
488 txn_id=None,
483 txn_id=None,
489 use_direct_calls=app_settings['vcs.hooks.direct_calls'])
484 use_direct_calls=app_settings['vcs.hooks.direct_calls'])
490
485
491 assert callback_daemon == daemon
486 assert callback_daemon == daemon
492 assert extras == extras
487 assert extras == extras
General Comments 0
You need to be logged in to leave comments. Login now