##// END OF EJS Templates
tests: fixed some tests because of wrong compare strings
super-admin -
r5097:ff28a588 default
parent child Browse files
Show More
@@ -1,375 +1,376 b''
1 1
2 2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 import logging
21 21 import io
22 22
23 23 import mock
24 24 import msgpack
25 25 import pytest
26 26
27 27 from rhodecode.lib import hooks_daemon
28 28 from rhodecode.lib.str_utils import safe_bytes
29 29 from rhodecode.tests.utils import assert_message_in_log
30 30 from rhodecode.lib.ext_json import json
31 31
32 32 test_proto = hooks_daemon.HooksHttpHandler.MSGPACK_HOOKS_PROTO
33 33
34 34
35 35 class TestDummyHooksCallbackDaemon(object):
36 36 def test_hooks_module_path_set_properly(self):
37 37 daemon = hooks_daemon.DummyHooksCallbackDaemon()
38 38 assert daemon.hooks_module == 'rhodecode.lib.hooks_daemon'
39 39
40 40 def test_logs_entering_the_hook(self):
41 41 daemon = hooks_daemon.DummyHooksCallbackDaemon()
42 42 with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
43 43 with daemon as return_value:
44 44 log_mock.assert_called_once_with(
45 45 'Running `%s` callback daemon', 'DummyHooksCallbackDaemon')
46 46 assert return_value == daemon
47 47
48 48 def test_logs_exiting_the_hook(self):
49 49 daemon = hooks_daemon.DummyHooksCallbackDaemon()
50 50 with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
51 51 with daemon:
52 52 pass
53 53 log_mock.assert_called_with(
54 54 'Exiting `%s` callback daemon', 'DummyHooksCallbackDaemon')
55 55
56 56
57 57 class TestHooks(object):
58 58 def test_hooks_can_be_used_as_a_context_processor(self):
59 59 hooks = hooks_daemon.Hooks()
60 60 with hooks as return_value:
61 61 pass
62 62 assert hooks == return_value
63 63
64 64
65 65 class TestHooksHttpHandler(object):
66 66 def test_read_request_parses_method_name_and_arguments(self):
67 67 data = {
68 68 'method': 'test',
69 69 'extras': {
70 70 'param1': 1,
71 71 'param2': 'a'
72 72 }
73 73 }
74 74 request = self._generate_post_request(data)
75 75 hooks_patcher = mock.patch.object(
76 76 hooks_daemon.Hooks, data['method'], create=True, return_value=1)
77 77
78 78 with hooks_patcher as hooks_mock:
79 79 handler = hooks_daemon.HooksHttpHandler
80 80 handler.DEFAULT_HOOKS_PROTO = test_proto
81 81 handler.wbufsize = 10240
82 82 MockServer(handler, request)
83 83
84 84 hooks_mock.assert_called_once_with(data['extras'])
85 85
86 86 def test_hooks_serialized_result_is_returned(self):
87 87 request = self._generate_post_request({})
88 88 rpc_method = 'test'
89 89 hook_result = {
90 90 'first': 'one',
91 91 'second': 2
92 92 }
93 93 extras = {}
94 94
95 95 # patching our _read to return test method and proto used
96 96 read_patcher = mock.patch.object(
97 97 hooks_daemon.HooksHttpHandler, '_read_request',
98 98 return_value=(test_proto, rpc_method, extras))
99 99
100 100 # patch Hooks instance to return hook_result data on 'test' call
101 101 hooks_patcher = mock.patch.object(
102 102 hooks_daemon.Hooks, rpc_method, create=True,
103 103 return_value=hook_result)
104 104
105 105 with read_patcher, hooks_patcher:
106 106 handler = hooks_daemon.HooksHttpHandler
107 107 handler.DEFAULT_HOOKS_PROTO = test_proto
108 108 handler.wbufsize = 10240
109 109 server = MockServer(handler, request)
110 110
111 111 expected_result = hooks_daemon.HooksHttpHandler.serialize_data(hook_result)
112 112
113 113 server.request.output_stream.seek(0)
114 114 assert server.request.output_stream.readlines()[-1] == expected_result
115 115
116 116 def test_exception_is_returned_in_response(self):
117 117 request = self._generate_post_request({})
118 118 rpc_method = 'test'
119 119
120 120 read_patcher = mock.patch.object(
121 121 hooks_daemon.HooksHttpHandler, '_read_request',
122 122 return_value=(test_proto, rpc_method, {}))
123 123
124 124 hooks_patcher = mock.patch.object(
125 125 hooks_daemon.Hooks, rpc_method, create=True,
126 126 side_effect=Exception('Test exception'))
127 127
128 128 with read_patcher, hooks_patcher:
129 129 handler = hooks_daemon.HooksHttpHandler
130 130 handler.DEFAULT_HOOKS_PROTO = test_proto
131 131 handler.wbufsize = 10240
132 132 server = MockServer(handler, request)
133 133
134 134 server.request.output_stream.seek(0)
135 135 data = server.request.output_stream.readlines()
136 136 msgpack_data = b''.join(data[5:])
137 137 org_exc = hooks_daemon.HooksHttpHandler.deserialize_data(msgpack_data)
138 138 expected_result = {
139 139 'exception': 'Exception',
140 140 'exception_traceback': org_exc['exception_traceback'],
141 141 'exception_args': ['Test exception']
142 142 }
143 143 assert org_exc == expected_result
144 144
145 145 def test_log_message_writes_to_debug_log(self, caplog):
146 146 ip_port = ('0.0.0.0', 8888)
147 147 handler = hooks_daemon.HooksHttpHandler(
148 148 MockRequest('POST /'), ip_port, mock.Mock())
149 149 fake_date = '1/Nov/2015 00:00:00'
150 150 date_patcher = mock.patch.object(
151 151 handler, 'log_date_time_string', return_value=fake_date)
152 152
153 153 with date_patcher, caplog.at_level(logging.DEBUG):
154 154 handler.log_message('Some message %d, %s', 123, 'string')
155 155
156 expected_message = f"HOOKS: {ip_port} - - [{fake_date}] Some message 123, string"
156 expected_message = f"HOOKS: client={ip_port} - - [{fake_date}] Some message 123, string"
157
157 158 assert_message_in_log(
158 159 caplog.records, expected_message,
159 160 levelno=logging.DEBUG, module='hooks_daemon')
160 161
161 162 def _generate_post_request(self, data, proto=test_proto):
162 163 if proto == hooks_daemon.HooksHttpHandler.MSGPACK_HOOKS_PROTO:
163 164 payload = msgpack.packb(data)
164 165 else:
165 166 payload = json.dumps(data)
166 167
167 168 return b'POST / HTTP/1.0\nContent-Length: %d\n\n%b' % (
168 169 len(payload), payload)
169 170
170 171
171 172 class ThreadedHookCallbackDaemon(object):
172 173 def test_constructor_calls_prepare(self):
173 174 prepare_daemon_patcher = mock.patch.object(
174 175 hooks_daemon.ThreadedHookCallbackDaemon, '_prepare')
175 176 with prepare_daemon_patcher as prepare_daemon_mock:
176 177 hooks_daemon.ThreadedHookCallbackDaemon()
177 178 prepare_daemon_mock.assert_called_once_with()
178 179
179 180 def test_run_is_called_on_context_start(self):
180 181 patchers = mock.patch.multiple(
181 182 hooks_daemon.ThreadedHookCallbackDaemon,
182 183 _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT)
183 184
184 185 with patchers as mocks:
185 186 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
186 187 with daemon as daemon_context:
187 188 pass
188 189 mocks['_run'].assert_called_once_with()
189 190 assert daemon_context == daemon
190 191
191 192 def test_stop_is_called_on_context_exit(self):
192 193 patchers = mock.patch.multiple(
193 194 hooks_daemon.ThreadedHookCallbackDaemon,
194 195 _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT)
195 196
196 197 with patchers as mocks:
197 198 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
198 199 with daemon as daemon_context:
199 200 assert mocks['_stop'].call_count == 0
200 201
201 202 mocks['_stop'].assert_called_once_with()
202 203 assert daemon_context == daemon
203 204
204 205
205 206 class TestHttpHooksCallbackDaemon(object):
206 207 def test_hooks_callback_generates_new_port(self, caplog):
207 208 with caplog.at_level(logging.DEBUG):
208 209 daemon = hooks_daemon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881)
209 210 assert daemon._daemon.server_address == ('127.0.0.1', 8881)
210 211
211 212 with caplog.at_level(logging.DEBUG):
212 213 daemon = hooks_daemon.HttpHooksCallbackDaemon(host=None, port=None)
213 214 assert daemon._daemon.server_address[1] in range(0, 66000)
214 215 assert daemon._daemon.server_address[0] != '127.0.0.1'
215 216
216 217 def test_prepare_inits_daemon_variable(self, tcp_server, caplog):
217 218 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
218 219 daemon = hooks_daemon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881)
219 220 assert daemon._daemon == tcp_server
220 221
221 222 _, port = tcp_server.server_address
222 223
223 224 msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \
224 225 f"hook object: <class 'rhodecode.lib.hooks_daemon.HooksHttpHandler'>"
225 226 assert_message_in_log(
226 227 caplog.records, msg, levelno=logging.DEBUG, module='hooks_daemon')
227 228
228 229 def test_prepare_inits_hooks_uri_and_logs_it(
229 230 self, tcp_server, caplog):
230 231 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
231 232 daemon = hooks_daemon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881)
232 233
233 234 _, port = tcp_server.server_address
234 235 expected_uri = '{}:{}'.format('127.0.0.1', port)
235 236 assert daemon.hooks_uri == expected_uri
236 237
237 238 msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \
238 239 f"hook object: <class 'rhodecode.lib.hooks_daemon.HooksHttpHandler'>"
239 240 assert_message_in_log(
240 241 caplog.records, msg,
241 242 levelno=logging.DEBUG, module='hooks_daemon')
242 243
243 244 def test_run_creates_a_thread(self, tcp_server):
244 245 thread = mock.Mock()
245 246
246 247 with self._tcp_patcher(tcp_server):
247 248 daemon = hooks_daemon.HttpHooksCallbackDaemon()
248 249
249 250 with self._thread_patcher(thread) as thread_mock:
250 251 daemon._run()
251 252
252 253 thread_mock.assert_called_once_with(
253 254 target=tcp_server.serve_forever,
254 255 kwargs={'poll_interval': daemon.POLL_INTERVAL})
255 256 assert thread.daemon is True
256 257 thread.start.assert_called_once_with()
257 258
258 259 def test_run_logs(self, tcp_server, caplog):
259 260
260 261 with self._tcp_patcher(tcp_server):
261 262 daemon = hooks_daemon.HttpHooksCallbackDaemon()
262 263
263 264 with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG):
264 265 daemon._run()
265 266
266 267 assert_message_in_log(
267 268 caplog.records,
268 'Running event loop of callback daemon in background thread',
269 'Running thread-based loop of callback daemon in background',
269 270 levelno=logging.DEBUG, module='hooks_daemon')
270 271
271 272 def test_stop_cleans_up_the_connection(self, tcp_server, caplog):
272 273 thread = mock.Mock()
273 274
274 275 with self._tcp_patcher(tcp_server):
275 276 daemon = hooks_daemon.HttpHooksCallbackDaemon()
276 277
277 278 with self._thread_patcher(thread), caplog.at_level(logging.DEBUG):
278 279 with daemon:
279 280 assert daemon._daemon == tcp_server
280 281 assert daemon._callback_thread == thread
281 282
282 283 assert daemon._daemon is None
283 284 assert daemon._callback_thread is None
284 285 tcp_server.shutdown.assert_called_with()
285 286 thread.join.assert_called_once_with()
286 287
287 288 assert_message_in_log(
288 289 caplog.records, 'Waiting for background thread to finish.',
289 290 levelno=logging.DEBUG, module='hooks_daemon')
290 291
291 292 def _tcp_patcher(self, tcp_server):
292 293 return mock.patch.object(
293 294 hooks_daemon, 'TCPServer', return_value=tcp_server)
294 295
295 296 def _thread_patcher(self, thread):
296 297 return mock.patch.object(
297 298 hooks_daemon.threading, 'Thread', return_value=thread)
298 299
299 300
300 301 class TestPrepareHooksDaemon(object):
301 302 @pytest.mark.parametrize('protocol', ('http',))
302 303 def test_returns_dummy_hooks_callback_daemon_when_using_direct_calls(
303 304 self, protocol):
304 305 expected_extras = {'extra1': 'value1'}
305 306 callback, extras = hooks_daemon.prepare_callback_daemon(
306 307 expected_extras.copy(), protocol=protocol,
307 308 host='127.0.0.1', use_direct_calls=True)
308 309 assert isinstance(callback, hooks_daemon.DummyHooksCallbackDaemon)
309 310 expected_extras['hooks_module'] = 'rhodecode.lib.hooks_daemon'
310 311 expected_extras['time'] = extras['time']
311 312 assert 'extra1' in extras
312 313
313 314 @pytest.mark.parametrize('protocol, expected_class', (
314 315 ('http', hooks_daemon.HttpHooksCallbackDaemon),
315 316 ))
316 317 def test_returns_real_hooks_callback_daemon_when_protocol_is_specified(
317 318 self, protocol, expected_class):
318 319 expected_extras = {
319 320 'extra1': 'value1',
320 321 'txn_id': 'txnid2',
321 322 'hooks_protocol': protocol.lower()
322 323 }
323 324 callback, extras = hooks_daemon.prepare_callback_daemon(
324 325 expected_extras.copy(), protocol=protocol, host='127.0.0.1',
325 326 use_direct_calls=False,
326 327 txn_id='txnid2')
327 328 assert isinstance(callback, expected_class)
328 329 extras.pop('hooks_uri')
329 330 expected_extras['time'] = extras['time']
330 331 assert extras == expected_extras
331 332
332 333 @pytest.mark.parametrize('protocol', (
333 334 'invalid',
334 335 'Http',
335 336 'HTTP',
336 337 ))
337 338 def test_raises_on_invalid_protocol(self, protocol):
338 339 expected_extras = {
339 340 'extra1': 'value1',
340 341 'hooks_protocol': protocol.lower()
341 342 }
342 343 with pytest.raises(Exception):
343 344 callback, extras = hooks_daemon.prepare_callback_daemon(
344 345 expected_extras.copy(),
345 346 protocol=protocol, host='127.0.0.1',
346 347 use_direct_calls=False)
347 348
348 349
349 350 class MockRequest(object):
350 351
351 352 def __init__(self, request):
352 353 self.request = request
353 354 self.input_stream = io.BytesIO(safe_bytes(self.request))
354 355 self.output_stream = io.BytesIO() # make it un-closable for testing invesitagion
355 356 self.output_stream.close = lambda: None
356 357
357 358 def makefile(self, mode, *args, **kwargs):
358 359 return self.output_stream if mode == 'wb' else self.input_stream
359 360
360 361
361 362 class MockServer(object):
362 363
363 364 def __init__(self, handler_cls, request):
364 365 ip_port = ('0.0.0.0', 8888)
365 366 self.request = MockRequest(request)
366 367 self.server_address = ip_port
367 368 self.handler = handler_cls(self.request, ip_port, self)
368 369
369 370
370 371 @pytest.fixture()
371 372 def tcp_server():
372 373 server = mock.Mock()
373 374 server.server_address = ('127.0.0.1', 8881)
374 375 server.wbufsize = 1024
375 376 return server
General Comments 0
You need to be logged in to leave comments. Login now