##// END OF EJS Templates
tests: Make return values of stub vcs controller editable.
Martin Bornhold -
r912:04974cb2 default
parent child Browse files
Show More
@@ -1,311 +1,310 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 base64
21 import base64
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25 import webtest.app
25 import webtest.app
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
31 from rhodecode.lib.middleware.utils import scm_app
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
33 from rhodecode.model.meta import Session
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 from rhodecode.tests.utils import set_anonymous_access
37 from rhodecode.tests.utils import set_anonymous_access
38
38
39
39
40 class StubVCSController(simplevcs.SimpleVCS):
40 class StubVCSController(simplevcs.SimpleVCS):
41
41
42 SCM = 'hg'
42 SCM = 'hg'
43 stub_response_body = tuple()
43 stub_response_body = tuple()
44
44
45 def __init__(self, *args, **kwargs):
45 def __init__(self, *args, **kwargs):
46 super(StubVCSController, self).__init__(*args, **kwargs)
46 super(StubVCSController, self).__init__(*args, **kwargs)
47 self.acl_repo_name = HG_REPO
47 self._action = 'pull'
48 self.url_repo_name = HG_REPO
48 self._name = HG_REPO
49 self.vcs_repo_name = HG_REPO
49 self.set_repo_names(None)
50 self.is_shadow_repo = False
51
50
52 def _get_repository_name(self, environ):
51 def _get_repository_name(self, environ):
53 return HG_REPO
52 return self._name
54
53
55 def _get_action(self, environ):
54 def _get_action(self, environ):
56 return "pull"
55 return self._action
57
56
58 def _create_wsgi_app(self, repo_path, repo_name, config):
57 def _create_wsgi_app(self, repo_path, repo_name, config):
59 def fake_app(environ, start_response):
58 def fake_app(environ, start_response):
60 start_response('200 OK', [])
59 start_response('200 OK', [])
61 return self.stub_response_body
60 return self.stub_response_body
62 return fake_app
61 return fake_app
63
62
64 def _create_config(self, extras, repo_name):
63 def _create_config(self, extras, repo_name):
65 return None
64 return None
66
65
67
66
68 @pytest.fixture
67 @pytest.fixture
69 def vcscontroller(pylonsapp, config_stub):
68 def vcscontroller(pylonsapp, config_stub):
70 config_stub.testing_securitypolicy()
69 config_stub.testing_securitypolicy()
71 config_stub.include('rhodecode.authentication')
70 config_stub.include('rhodecode.authentication')
72
71
73 set_anonymous_access(True)
72 set_anonymous_access(True)
74 controller = StubVCSController(pylonsapp, pylonsapp.config, None)
73 controller = StubVCSController(pylonsapp, pylonsapp.config, None)
75 app = HttpsFixup(controller, pylonsapp.config)
74 app = HttpsFixup(controller, pylonsapp.config)
76 app = webtest.app.TestApp(app)
75 app = webtest.app.TestApp(app)
77
76
78 _remove_default_user_from_query_cache()
77 _remove_default_user_from_query_cache()
79
78
80 # Sanity checks that things are set up correctly
79 # Sanity checks that things are set up correctly
81 app.get('/' + HG_REPO, status=200)
80 app.get('/' + HG_REPO, status=200)
82
81
83 app.controller = controller
82 app.controller = controller
84 return app
83 return app
85
84
86
85
87 def _remove_default_user_from_query_cache():
86 def _remove_default_user_from_query_cache():
88 user = User.get_default_user(cache=True)
87 user = User.get_default_user(cache=True)
89 query = Session().query(User).filter(User.username == user.username)
88 query = Session().query(User).filter(User.username == user.username)
90 query = query.options(FromCache(
89 query = query.options(FromCache(
91 "sql_cache_short", "get_user_%s" % _hash_key(user.username)))
90 "sql_cache_short", "get_user_%s" % _hash_key(user.username)))
92 query.invalidate()
91 query.invalidate()
93 Session().expire(user)
92 Session().expire(user)
94
93
95
94
96 @pytest.fixture
95 @pytest.fixture
97 def disable_anonymous_user(request, pylonsapp):
96 def disable_anonymous_user(request, pylonsapp):
98 set_anonymous_access(False)
97 set_anonymous_access(False)
99
98
100 @request.addfinalizer
99 @request.addfinalizer
101 def cleanup():
100 def cleanup():
102 set_anonymous_access(True)
101 set_anonymous_access(True)
103
102
104
103
105 def test_handles_exceptions_during_permissions_checks(
104 def test_handles_exceptions_during_permissions_checks(
106 vcscontroller, disable_anonymous_user):
105 vcscontroller, disable_anonymous_user):
107 user_and_pass = '%s:%s' % (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
106 user_and_pass = '%s:%s' % (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
108 auth_password = base64.encodestring(user_and_pass).strip()
107 auth_password = base64.encodestring(user_and_pass).strip()
109 extra_environ = {
108 extra_environ = {
110 'AUTH_TYPE': 'Basic',
109 'AUTH_TYPE': 'Basic',
111 'HTTP_AUTHORIZATION': 'Basic %s' % auth_password,
110 'HTTP_AUTHORIZATION': 'Basic %s' % auth_password,
112 'REMOTE_USER': TEST_USER_ADMIN_LOGIN,
111 'REMOTE_USER': TEST_USER_ADMIN_LOGIN,
113 }
112 }
114
113
115 # Verify that things are hooked up correctly
114 # Verify that things are hooked up correctly
116 vcscontroller.get('/', status=200, extra_environ=extra_environ)
115 vcscontroller.get('/', status=200, extra_environ=extra_environ)
117
116
118 # Simulate trouble during permission checks
117 # Simulate trouble during permission checks
119 with mock.patch('rhodecode.model.db.User.get_by_username',
118 with mock.patch('rhodecode.model.db.User.get_by_username',
120 side_effect=Exception) as get_user:
119 side_effect=Exception) as get_user:
121 # Verify that a correct 500 is returned and check that the expected
120 # Verify that a correct 500 is returned and check that the expected
122 # code path was hit.
121 # code path was hit.
123 vcscontroller.get('/', status=500, extra_environ=extra_environ)
122 vcscontroller.get('/', status=500, extra_environ=extra_environ)
124 assert get_user.called
123 assert get_user.called
125
124
126
125
127 def test_returns_forbidden_if_no_anonymous_access(
126 def test_returns_forbidden_if_no_anonymous_access(
128 vcscontroller, disable_anonymous_user):
127 vcscontroller, disable_anonymous_user):
129 vcscontroller.get('/', status=401)
128 vcscontroller.get('/', status=401)
130
129
131
130
132 class StubFailVCSController(simplevcs.SimpleVCS):
131 class StubFailVCSController(simplevcs.SimpleVCS):
133 def _handle_request(self, environ, start_response):
132 def _handle_request(self, environ, start_response):
134 raise Exception("BOOM")
133 raise Exception("BOOM")
135
134
136
135
137 @pytest.fixture(scope='module')
136 @pytest.fixture(scope='module')
138 def fail_controller(pylonsapp):
137 def fail_controller(pylonsapp):
139 controller = StubFailVCSController(pylonsapp, pylonsapp.config, None)
138 controller = StubFailVCSController(pylonsapp, pylonsapp.config, None)
140 controller = HttpsFixup(controller, pylonsapp.config)
139 controller = HttpsFixup(controller, pylonsapp.config)
141 controller = webtest.app.TestApp(controller)
140 controller = webtest.app.TestApp(controller)
142 return controller
141 return controller
143
142
144
143
145 def test_handles_exceptions_as_internal_server_error(fail_controller):
144 def test_handles_exceptions_as_internal_server_error(fail_controller):
146 fail_controller.get('/', status=500)
145 fail_controller.get('/', status=500)
147
146
148
147
149 def test_provides_traceback_for_appenlight(fail_controller):
148 def test_provides_traceback_for_appenlight(fail_controller):
150 response = fail_controller.get(
149 response = fail_controller.get(
151 '/', status=500, extra_environ={'appenlight.client': 'fake'})
150 '/', status=500, extra_environ={'appenlight.client': 'fake'})
152 assert 'appenlight.__traceback' in response.request.environ
151 assert 'appenlight.__traceback' in response.request.environ
153
152
154
153
155 def test_provides_utils_scm_app_as_scm_app_by_default(pylonsapp):
154 def test_provides_utils_scm_app_as_scm_app_by_default(pylonsapp):
156 controller = StubVCSController(pylonsapp, pylonsapp.config, None)
155 controller = StubVCSController(pylonsapp, pylonsapp.config, None)
157 assert controller.scm_app is scm_app
156 assert controller.scm_app is scm_app
158
157
159
158
160 def test_allows_to_override_scm_app_via_config(pylonsapp):
159 def test_allows_to_override_scm_app_via_config(pylonsapp):
161 config = pylonsapp.config.copy()
160 config = pylonsapp.config.copy()
162 config['vcs.scm_app_implementation'] = (
161 config['vcs.scm_app_implementation'] = (
163 'rhodecode.tests.lib.middleware.mock_scm_app')
162 'rhodecode.tests.lib.middleware.mock_scm_app')
164 controller = StubVCSController(pylonsapp, config, None)
163 controller = StubVCSController(pylonsapp, config, None)
165 assert controller.scm_app is mock_scm_app
164 assert controller.scm_app is mock_scm_app
166
165
167
166
168 @pytest.mark.parametrize('query_string, expected', [
167 @pytest.mark.parametrize('query_string, expected', [
169 ('cmd=stub_command', True),
168 ('cmd=stub_command', True),
170 ('cmd=listkeys', False),
169 ('cmd=listkeys', False),
171 ])
170 ])
172 def test_should_check_locking(query_string, expected):
171 def test_should_check_locking(query_string, expected):
173 result = simplevcs._should_check_locking(query_string)
172 result = simplevcs._should_check_locking(query_string)
174 assert result == expected
173 assert result == expected
175
174
176
175
177 @mock.patch.multiple(
176 @mock.patch.multiple(
178 'Pyro4.config', SERVERTYPE='multiplex', POLLTIMEOUT=0.01)
177 'Pyro4.config', SERVERTYPE='multiplex', POLLTIMEOUT=0.01)
179 class TestGenerateVcsResponse:
178 class TestGenerateVcsResponse:
180
179
181 def test_ensures_that_start_response_is_called_early_enough(self):
180 def test_ensures_that_start_response_is_called_early_enough(self):
182 self.call_controller_with_response_body(iter(['a', 'b']))
181 self.call_controller_with_response_body(iter(['a', 'b']))
183 assert self.start_response.called
182 assert self.start_response.called
184
183
185 def test_invalidates_cache_after_body_is_consumed(self):
184 def test_invalidates_cache_after_body_is_consumed(self):
186 result = self.call_controller_with_response_body(iter(['a', 'b']))
185 result = self.call_controller_with_response_body(iter(['a', 'b']))
187 assert not self.was_cache_invalidated()
186 assert not self.was_cache_invalidated()
188 # Consume the result
187 # Consume the result
189 list(result)
188 list(result)
190 assert self.was_cache_invalidated()
189 assert self.was_cache_invalidated()
191
190
192 @mock.patch('rhodecode.lib.middleware.simplevcs.HTTPLockedRC')
191 @mock.patch('rhodecode.lib.middleware.simplevcs.HTTPLockedRC')
193 def test_handles_locking_exception(self, http_locked_rc):
192 def test_handles_locking_exception(self, http_locked_rc):
194 result = self.call_controller_with_response_body(
193 result = self.call_controller_with_response_body(
195 self.raise_result_iter(vcs_kind='repo_locked'))
194 self.raise_result_iter(vcs_kind='repo_locked'))
196 assert not http_locked_rc.called
195 assert not http_locked_rc.called
197 # Consume the result
196 # Consume the result
198 list(result)
197 list(result)
199 assert http_locked_rc.called
198 assert http_locked_rc.called
200
199
201 @mock.patch('rhodecode.lib.middleware.simplevcs.HTTPRequirementError')
200 @mock.patch('rhodecode.lib.middleware.simplevcs.HTTPRequirementError')
202 def test_handles_requirement_exception(self, http_requirement):
201 def test_handles_requirement_exception(self, http_requirement):
203 result = self.call_controller_with_response_body(
202 result = self.call_controller_with_response_body(
204 self.raise_result_iter(vcs_kind='requirement'))
203 self.raise_result_iter(vcs_kind='requirement'))
205 assert not http_requirement.called
204 assert not http_requirement.called
206 # Consume the result
205 # Consume the result
207 list(result)
206 list(result)
208 assert http_requirement.called
207 assert http_requirement.called
209
208
210 @mock.patch('rhodecode.lib.middleware.simplevcs.HTTPLockedRC')
209 @mock.patch('rhodecode.lib.middleware.simplevcs.HTTPLockedRC')
211 def test_handles_locking_exception_in_app_call(self, http_locked_rc):
210 def test_handles_locking_exception_in_app_call(self, http_locked_rc):
212 app_factory_patcher = mock.patch.object(
211 app_factory_patcher = mock.patch.object(
213 StubVCSController, '_create_wsgi_app')
212 StubVCSController, '_create_wsgi_app')
214 with app_factory_patcher as app_factory:
213 with app_factory_patcher as app_factory:
215 app_factory().side_effect = self.vcs_exception()
214 app_factory().side_effect = self.vcs_exception()
216 result = self.call_controller_with_response_body(['a'])
215 result = self.call_controller_with_response_body(['a'])
217 list(result)
216 list(result)
218 assert http_locked_rc.called
217 assert http_locked_rc.called
219
218
220 def test_raises_unknown_exceptions(self):
219 def test_raises_unknown_exceptions(self):
221 result = self.call_controller_with_response_body(
220 result = self.call_controller_with_response_body(
222 self.raise_result_iter(vcs_kind='unknown'))
221 self.raise_result_iter(vcs_kind='unknown'))
223 with pytest.raises(Exception):
222 with pytest.raises(Exception):
224 list(result)
223 list(result)
225
224
226 def test_prepare_callback_daemon_is_called(self):
225 def test_prepare_callback_daemon_is_called(self):
227 def side_effect(extras):
226 def side_effect(extras):
228 return DummyHooksCallbackDaemon(), extras
227 return DummyHooksCallbackDaemon(), extras
229
228
230 prepare_patcher = mock.patch.object(
229 prepare_patcher = mock.patch.object(
231 StubVCSController, '_prepare_callback_daemon')
230 StubVCSController, '_prepare_callback_daemon')
232 with prepare_patcher as prepare_mock:
231 with prepare_patcher as prepare_mock:
233 prepare_mock.side_effect = side_effect
232 prepare_mock.side_effect = side_effect
234 self.call_controller_with_response_body(iter(['a', 'b']))
233 self.call_controller_with_response_body(iter(['a', 'b']))
235 assert prepare_mock.called
234 assert prepare_mock.called
236 assert prepare_mock.call_count == 1
235 assert prepare_mock.call_count == 1
237
236
238 def call_controller_with_response_body(self, response_body):
237 def call_controller_with_response_body(self, response_body):
239 settings = {
238 settings = {
240 'base_path': 'fake_base_path',
239 'base_path': 'fake_base_path',
241 'vcs.hooks.protocol': 'http',
240 'vcs.hooks.protocol': 'http',
242 'vcs.hooks.direct_calls': False,
241 'vcs.hooks.direct_calls': False,
243 }
242 }
244 controller = StubVCSController(None, settings, None)
243 controller = StubVCSController(None, settings, None)
245 controller._invalidate_cache = mock.Mock()
244 controller._invalidate_cache = mock.Mock()
246 controller.stub_response_body = response_body
245 controller.stub_response_body = response_body
247 self.start_response = mock.Mock()
246 self.start_response = mock.Mock()
248 result = controller._generate_vcs_response(
247 result = controller._generate_vcs_response(
249 environ={}, start_response=self.start_response,
248 environ={}, start_response=self.start_response,
250 repo_path='fake_repo_path',
249 repo_path='fake_repo_path',
251 repo_name='fake_repo_name',
250 repo_name='fake_repo_name',
252 extras={}, action='push')
251 extras={}, action='push')
253 self.controller = controller
252 self.controller = controller
254 return result
253 return result
255
254
256 def raise_result_iter(self, vcs_kind='repo_locked'):
255 def raise_result_iter(self, vcs_kind='repo_locked'):
257 """
256 """
258 Simulates an exception due to a vcs raised exception if kind vcs_kind
257 Simulates an exception due to a vcs raised exception if kind vcs_kind
259 """
258 """
260 raise self.vcs_exception(vcs_kind=vcs_kind)
259 raise self.vcs_exception(vcs_kind=vcs_kind)
261 yield "never_reached"
260 yield "never_reached"
262
261
263 def vcs_exception(self, vcs_kind='repo_locked'):
262 def vcs_exception(self, vcs_kind='repo_locked'):
264 locked_exception = Exception('TEST_MESSAGE')
263 locked_exception = Exception('TEST_MESSAGE')
265 locked_exception._vcs_kind = vcs_kind
264 locked_exception._vcs_kind = vcs_kind
266 return locked_exception
265 return locked_exception
267
266
268 def was_cache_invalidated(self):
267 def was_cache_invalidated(self):
269 return self.controller._invalidate_cache.called
268 return self.controller._invalidate_cache.called
270
269
271
270
272 class TestInitializeGenerator:
271 class TestInitializeGenerator:
273
272
274 def test_drains_first_element(self):
273 def test_drains_first_element(self):
275 gen = self.factory(['__init__', 1, 2])
274 gen = self.factory(['__init__', 1, 2])
276 result = list(gen)
275 result = list(gen)
277 assert result == [1, 2]
276 assert result == [1, 2]
278
277
279 @pytest.mark.parametrize('values', [
278 @pytest.mark.parametrize('values', [
280 [],
279 [],
281 [1, 2],
280 [1, 2],
282 ])
281 ])
283 def test_raises_value_error(self, values):
282 def test_raises_value_error(self, values):
284 with pytest.raises(ValueError):
283 with pytest.raises(ValueError):
285 self.factory(values)
284 self.factory(values)
286
285
287 @simplevcs.initialize_generator
286 @simplevcs.initialize_generator
288 def factory(self, iterable):
287 def factory(self, iterable):
289 for elem in iterable:
288 for elem in iterable:
290 yield elem
289 yield elem
291
290
292
291
293 class TestPrepareHooksDaemon(object):
292 class TestPrepareHooksDaemon(object):
294 def test_calls_imported_prepare_callback_daemon(self, app_settings):
293 def test_calls_imported_prepare_callback_daemon(self, app_settings):
295 expected_extras = {'extra1': 'value1'}
294 expected_extras = {'extra1': 'value1'}
296 daemon = DummyHooksCallbackDaemon()
295 daemon = DummyHooksCallbackDaemon()
297
296
298 controller = StubVCSController(None, app_settings, None)
297 controller = StubVCSController(None, app_settings, None)
299 prepare_patcher = mock.patch.object(
298 prepare_patcher = mock.patch.object(
300 simplevcs, 'prepare_callback_daemon',
299 simplevcs, 'prepare_callback_daemon',
301 return_value=(daemon, expected_extras))
300 return_value=(daemon, expected_extras))
302 with prepare_patcher as prepare_mock:
301 with prepare_patcher as prepare_mock:
303 callback_daemon, extras = controller._prepare_callback_daemon(
302 callback_daemon, extras = controller._prepare_callback_daemon(
304 expected_extras.copy())
303 expected_extras.copy())
305 prepare_mock.assert_called_once_with(
304 prepare_mock.assert_called_once_with(
306 expected_extras,
305 expected_extras,
307 protocol=app_settings['vcs.hooks.protocol'],
306 protocol=app_settings['vcs.hooks.protocol'],
308 use_direct_calls=app_settings['vcs.hooks.direct_calls'])
307 use_direct_calls=app_settings['vcs.hooks.direct_calls'])
309
308
310 assert callback_daemon == daemon
309 assert callback_daemon == daemon
311 assert extras == extras
310 assert extras == extras
General Comments 0
You need to be logged in to leave comments. Login now