##// 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,155 +1,155 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 urllib.parse
20 import urllib.parse
21
21
22 import mock
22 import mock
23 import pytest
23 import pytest
24 import simplejson as json
24 import simplejson as json
25
25
26 from rhodecode.lib.vcs.backends.base import Config
26 from rhodecode.lib.vcs.backends.base import Config
27 from rhodecode.tests.lib.middleware import mock_scm_app
27 from rhodecode.tests.lib.middleware import mock_scm_app
28 import rhodecode.lib.middleware.simplehg as simplehg
28 import rhodecode.lib.middleware.simplehg as simplehg
29
29
30
30
31 def get_environ(url):
31 def get_environ(url):
32 """Construct a minimum WSGI environ based on the URL."""
32 """Construct a minimum WSGI environ based on the URL."""
33 parsed_url = urllib.parse.urlparse(url)
33 parsed_url = urllib.parse.urlparse(url)
34 environ = {
34 environ = {
35 'PATH_INFO': parsed_url.path,
35 'PATH_INFO': parsed_url.path,
36 'QUERY_STRING': parsed_url.query,
36 'QUERY_STRING': parsed_url.query,
37 }
37 }
38
38
39 return environ
39 return environ
40
40
41
41
42 @pytest.mark.parametrize(
42 @pytest.mark.parametrize(
43 'url, expected_action',
43 'url, expected_action',
44 [
44 [
45 ('/foo/bar?cmd=unbundle&key=tip', 'push'),
45 ('/foo/bar?cmd=unbundle&key=tip', 'push'),
46 ('/foo/bar?cmd=pushkey&key=tip', 'push'),
46 ('/foo/bar?cmd=pushkey&key=tip', 'push'),
47 ('/foo/bar?cmd=listkeys&key=tip', 'pull'),
47 ('/foo/bar?cmd=listkeys&key=tip', 'pull'),
48 ('/foo/bar?cmd=changegroup&key=tip', 'pull'),
48 ('/foo/bar?cmd=changegroup&key=tip', 'pull'),
49 ('/foo/bar?cmd=hello', 'pull'),
49 ('/foo/bar?cmd=hello', 'pull'),
50 ('/foo/bar?cmd=batch', 'push'),
50 ('/foo/bar?cmd=batch', 'push'),
51 ('/foo/bar?cmd=putlfile', 'push'),
51 ('/foo/bar?cmd=putlfile', 'push'),
52 # Edge case: unknown argument: assume push
52 # Edge case: unknown argument: assume push
53 ('/foo/bar?cmd=unknown&key=tip', 'push'),
53 ('/foo/bar?cmd=unknown&key=tip', 'push'),
54 ('/foo/bar?cmd=&key=tip', 'push'),
54 ('/foo/bar?cmd=&key=tip', 'push'),
55 # Edge case: not cmd argument
55 # Edge case: not cmd argument
56 ('/foo/bar?key=tip', 'push'),
56 ('/foo/bar?key=tip', 'push'),
57 ])
57 ])
58 def test_get_action(url, expected_action, request_stub):
58 def test_get_action(url, expected_action, request_stub):
59 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
59 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
60 registry=request_stub.registry)
60 registry=request_stub.registry)
61 assert expected_action == app._get_action(get_environ(url))
61 assert expected_action == app._get_action(get_environ(url))
62
62
63
63
64 @pytest.mark.parametrize(
64 @pytest.mark.parametrize(
65 'environ, expected_xargs, expected_batch',
65 'environ, expected_xargs, expected_batch',
66 [
66 [
67 ({},
67 ({},
68 [''], ['push']),
68 [''], ['push']),
69
69
70 ({'HTTP_X_HGARG_1': ''},
70 ({'HTTP_X_HGARG_1': ''},
71 [''], ['push']),
71 [''], ['push']),
72
72
73 ({'HTTP_X_HGARG_1': 'cmds=listkeys+namespace%3Dphases'},
73 ({'HTTP_X_HGARG_1': 'cmds=listkeys+namespace%3Dphases'},
74 ['listkeys namespace=phases'], ['pull']),
74 ['listkeys namespace=phases'], ['pull']),
75
75
76 ({'HTTP_X_HGARG_1': 'cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'},
76 ({'HTTP_X_HGARG_1': 'cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'},
77 ['pushkey namespace=bookmarks,key=bm,old=,new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'], ['push']),
77 ['pushkey namespace=bookmarks,key=bm,old=,new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'], ['push']),
78
78
79 ({'HTTP_X_HGARG_1': 'namespace=phases'},
79 ({'HTTP_X_HGARG_1': 'namespace=phases'},
80 ['namespace=phases'], ['push']),
80 ['namespace=phases'], ['push']),
81
81
82 ])
82 ])
83 def test_xarg_and_batch_commands(environ, expected_xargs, expected_batch):
83 def test_xarg_and_batch_commands(environ, expected_xargs, expected_batch):
84 app = simplehg.SimpleHg
84 app = simplehg.SimpleHg
85
85
86 result = app._get_xarg_headers(environ)
86 result = app._get_xarg_headers(environ)
87 result_batch = app._get_batch_cmd(environ)
87 result_batch = app._get_batch_cmd(environ)
88 assert expected_xargs == result
88 assert expected_xargs == result
89 assert expected_batch == result_batch
89 assert expected_batch == result_batch
90
90
91
91
92 @pytest.mark.parametrize(
92 @pytest.mark.parametrize(
93 'url, expected_repo_name',
93 'url, expected_repo_name',
94 [
94 [
95 ('/foo?cmd=unbundle&key=tip', 'foo'),
95 ('/foo?cmd=unbundle&key=tip', 'foo'),
96 ('/foo/bar?cmd=pushkey&key=tip', 'foo/bar'),
96 ('/foo/bar?cmd=pushkey&key=tip', 'foo/bar'),
97 ('/foo/bar/baz?cmd=listkeys&key=tip', 'foo/bar/baz'),
97 ('/foo/bar/baz?cmd=listkeys&key=tip', 'foo/bar/baz'),
98 # Repos with trailing slashes.
98 # Repos with trailing slashes.
99 ('/foo/?cmd=unbundle&key=tip', 'foo'),
99 ('/foo/?cmd=unbundle&key=tip', 'foo'),
100 ('/foo/bar/?cmd=pushkey&key=tip', 'foo/bar'),
100 ('/foo/bar/?cmd=pushkey&key=tip', 'foo/bar'),
101 ('/foo/bar/baz/?cmd=listkeys&key=tip', 'foo/bar/baz'),
101 ('/foo/bar/baz/?cmd=listkeys&key=tip', 'foo/bar/baz'),
102 ])
102 ])
103 def test_get_repository_name(url, expected_repo_name, request_stub):
103 def test_get_repository_name(url, expected_repo_name, request_stub):
104 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
104 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
105 registry=request_stub.registry)
105 registry=request_stub.registry)
106 assert expected_repo_name == app._get_repository_name(get_environ(url))
106 assert expected_repo_name == app._get_repository_name(get_environ(url))
107
107
108
108
109 def test_get_config(user_util, baseapp, request_stub):
109 def test_get_config(user_util, baseapp, request_stub):
110 repo = user_util.create_repo(repo_type='git')
110 repo = user_util.create_repo(repo_type='git')
111 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
111 app = simplehg.SimpleHg(config={'auth_ret_code': '', 'base_path': ''},
112 registry=request_stub.registry)
112 registry=request_stub.registry)
113 extras = [('foo', 'FOO', 'bar', 'BAR')]
113 extras = [('foo', 'FOO', 'bar', 'BAR')]
114
114
115 hg_config = app._create_config(extras, repo_name=repo.repo_name)
115 hg_config = app._create_config(extras, repo_name=repo.repo_name)
116
116
117 config = simplehg.utils.make_db_config(repo=repo.repo_name)
117 config = simplehg.utils.make_db_config(repo=repo.repo_name)
118 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
118 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
119 hg_config_org = config
119 hg_config_org = config
120
120
121 expected_config = [
121 expected_config = [
122 ('vcs_svn_tag', 'ff89f8c714d135d865f44b90e5413b88de19a55f', '/tags/*'),
122 ('vcs_svn_tag', 'ff89f8c714d135d865f44b90e5413b88de19a55f', '/tags/*'),
123 ('web', 'push_ssl', 'False'),
123 ('web', 'push_ssl', 'False'),
124 ('web', 'allow_push', '*'),
124 ('web', 'allow_push', '*'),
125 ('web', 'allow_archive', 'gz zip bz2'),
125 ('web', 'allow_archive', 'gz zip bz2'),
126 ('web', 'baseurl', '/'),
126 ('web', 'baseurl', '/'),
127 ('vcs_git_lfs', 'store_location', hg_config_org.get('vcs_git_lfs', 'store_location')),
127 ('vcs_git_lfs', 'store_location', hg_config_org.get('vcs_git_lfs', 'store_location')),
128 ('vcs_svn_branch', '9aac1a38c3b8a0cdc4ae0f960a5f83332bc4fa5e', '/branches/*'),
128 ('vcs_svn_branch', '9aac1a38c3b8a0cdc4ae0f960a5f83332bc4fa5e', '/branches/*'),
129 ('vcs_svn_branch', 'c7e6a611c87da06529fd0dd733308481d67c71a8', '/trunk'),
129 ('vcs_svn_branch', 'c7e6a611c87da06529fd0dd733308481d67c71a8', '/trunk'),
130 ('largefiles', 'usercache', hg_config_org.get('largefiles', 'usercache')),
130 ('largefiles', 'usercache', hg_config_org.get('largefiles', 'usercache')),
131 ('hooks', 'preoutgoing.pre_pull', 'python:vcsserver.hooks.pre_pull'),
131 ('hooks', 'preoutgoing.pre_pull', 'python:vcsserver.hooks.pre_pull'),
132 ('hooks', 'prechangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
132 ('hooks', 'prechangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
133 ('hooks', 'outgoing.pull_logger', 'python:vcsserver.hooks.log_pull_action'),
133 ('hooks', 'outgoing.pull_logger', 'python:vcsserver.hooks.log_pull_action'),
134 ('hooks', 'pretxnchangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
134 ('hooks', 'pretxnchangegroup.pre_push', 'python:vcsserver.hooks.pre_push'),
135 ('hooks', 'changegroup.push_logger', 'python:vcsserver.hooks.log_push_action'),
135 ('hooks', 'changegroup.push_logger', 'python:vcsserver.hooks.log_push_action'),
136 ('hooks', 'changegroup.repo_size', 'python:vcsserver.hooks.repo_size'),
136 ('hooks', 'changegroup.repo_size', 'python:vcsserver.hooks.repo_size'),
137 ('phases', 'publish', 'True'),
137 ('phases', 'publish', 'True'),
138 ('extensions', 'largefiles', ''),
138 ('extensions', 'largefiles', ''),
139 ('paths', '/', hg_config_org.get('paths', '/')),
139 ('paths', '/', hg_config_org.get('paths', '/')),
140 ('rhodecode', 'RC_SCM_DATA', '[["foo", "FOO", "bar", "BAR"]]')
140 ('rhodecode', 'RC_SCM_DATA', '[["foo","FOO","bar","BAR"]]')
141 ]
141 ]
142 for entry in expected_config:
142 for entry in expected_config:
143 assert entry in hg_config
143 assert entry in hg_config
144
144
145
145
146 def test_create_wsgi_app_uses_scm_app_from_simplevcs(request_stub):
146 def test_create_wsgi_app_uses_scm_app_from_simplevcs(request_stub):
147 config = {
147 config = {
148 'auth_ret_code': '',
148 'auth_ret_code': '',
149 'base_path': '',
149 'base_path': '',
150 'vcs.scm_app_implementation':
150 'vcs.scm_app_implementation':
151 'rhodecode.tests.lib.middleware.mock_scm_app',
151 'rhodecode.tests.lib.middleware.mock_scm_app',
152 }
152 }
153 app = simplehg.SimpleHg(config=config, registry=request_stub.registry)
153 app = simplehg.SimpleHg(config=config, registry=request_stub.registry)
154 wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {})
154 wsgi_app = app._create_wsgi_app('/tmp/test', 'test_repo', {})
155 assert wsgi_app is mock_scm_app.mock_hg_wsgi
155 assert wsgi_app is mock_scm_app.mock_hg_wsgi
@@ -1,375 +1,376 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 logging
20 import logging
21 import io
21 import io
22
22
23 import mock
23 import mock
24 import msgpack
24 import msgpack
25 import pytest
25 import pytest
26
26
27 from rhodecode.lib import hooks_daemon
27 from rhodecode.lib import hooks_daemon
28 from rhodecode.lib.str_utils import safe_bytes
28 from rhodecode.lib.str_utils import safe_bytes
29 from rhodecode.tests.utils import assert_message_in_log
29 from rhodecode.tests.utils import assert_message_in_log
30 from rhodecode.lib.ext_json import json
30 from rhodecode.lib.ext_json import json
31
31
32 test_proto = hooks_daemon.HooksHttpHandler.MSGPACK_HOOKS_PROTO
32 test_proto = hooks_daemon.HooksHttpHandler.MSGPACK_HOOKS_PROTO
33
33
34
34
35 class TestDummyHooksCallbackDaemon(object):
35 class TestDummyHooksCallbackDaemon(object):
36 def test_hooks_module_path_set_properly(self):
36 def test_hooks_module_path_set_properly(self):
37 daemon = hooks_daemon.DummyHooksCallbackDaemon()
37 daemon = hooks_daemon.DummyHooksCallbackDaemon()
38 assert daemon.hooks_module == 'rhodecode.lib.hooks_daemon'
38 assert daemon.hooks_module == 'rhodecode.lib.hooks_daemon'
39
39
40 def test_logs_entering_the_hook(self):
40 def test_logs_entering_the_hook(self):
41 daemon = hooks_daemon.DummyHooksCallbackDaemon()
41 daemon = hooks_daemon.DummyHooksCallbackDaemon()
42 with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
42 with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
43 with daemon as return_value:
43 with daemon as return_value:
44 log_mock.assert_called_once_with(
44 log_mock.assert_called_once_with(
45 'Running `%s` callback daemon', 'DummyHooksCallbackDaemon')
45 'Running `%s` callback daemon', 'DummyHooksCallbackDaemon')
46 assert return_value == daemon
46 assert return_value == daemon
47
47
48 def test_logs_exiting_the_hook(self):
48 def test_logs_exiting_the_hook(self):
49 daemon = hooks_daemon.DummyHooksCallbackDaemon()
49 daemon = hooks_daemon.DummyHooksCallbackDaemon()
50 with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
50 with mock.patch.object(hooks_daemon.log, 'debug') as log_mock:
51 with daemon:
51 with daemon:
52 pass
52 pass
53 log_mock.assert_called_with(
53 log_mock.assert_called_with(
54 'Exiting `%s` callback daemon', 'DummyHooksCallbackDaemon')
54 'Exiting `%s` callback daemon', 'DummyHooksCallbackDaemon')
55
55
56
56
57 class TestHooks(object):
57 class TestHooks(object):
58 def test_hooks_can_be_used_as_a_context_processor(self):
58 def test_hooks_can_be_used_as_a_context_processor(self):
59 hooks = hooks_daemon.Hooks()
59 hooks = hooks_daemon.Hooks()
60 with hooks as return_value:
60 with hooks as return_value:
61 pass
61 pass
62 assert hooks == return_value
62 assert hooks == return_value
63
63
64
64
65 class TestHooksHttpHandler(object):
65 class TestHooksHttpHandler(object):
66 def test_read_request_parses_method_name_and_arguments(self):
66 def test_read_request_parses_method_name_and_arguments(self):
67 data = {
67 data = {
68 'method': 'test',
68 'method': 'test',
69 'extras': {
69 'extras': {
70 'param1': 1,
70 'param1': 1,
71 'param2': 'a'
71 'param2': 'a'
72 }
72 }
73 }
73 }
74 request = self._generate_post_request(data)
74 request = self._generate_post_request(data)
75 hooks_patcher = mock.patch.object(
75 hooks_patcher = mock.patch.object(
76 hooks_daemon.Hooks, data['method'], create=True, return_value=1)
76 hooks_daemon.Hooks, data['method'], create=True, return_value=1)
77
77
78 with hooks_patcher as hooks_mock:
78 with hooks_patcher as hooks_mock:
79 handler = hooks_daemon.HooksHttpHandler
79 handler = hooks_daemon.HooksHttpHandler
80 handler.DEFAULT_HOOKS_PROTO = test_proto
80 handler.DEFAULT_HOOKS_PROTO = test_proto
81 handler.wbufsize = 10240
81 handler.wbufsize = 10240
82 MockServer(handler, request)
82 MockServer(handler, request)
83
83
84 hooks_mock.assert_called_once_with(data['extras'])
84 hooks_mock.assert_called_once_with(data['extras'])
85
85
86 def test_hooks_serialized_result_is_returned(self):
86 def test_hooks_serialized_result_is_returned(self):
87 request = self._generate_post_request({})
87 request = self._generate_post_request({})
88 rpc_method = 'test'
88 rpc_method = 'test'
89 hook_result = {
89 hook_result = {
90 'first': 'one',
90 'first': 'one',
91 'second': 2
91 'second': 2
92 }
92 }
93 extras = {}
93 extras = {}
94
94
95 # patching our _read to return test method and proto used
95 # patching our _read to return test method and proto used
96 read_patcher = mock.patch.object(
96 read_patcher = mock.patch.object(
97 hooks_daemon.HooksHttpHandler, '_read_request',
97 hooks_daemon.HooksHttpHandler, '_read_request',
98 return_value=(test_proto, rpc_method, extras))
98 return_value=(test_proto, rpc_method, extras))
99
99
100 # patch Hooks instance to return hook_result data on 'test' call
100 # patch Hooks instance to return hook_result data on 'test' call
101 hooks_patcher = mock.patch.object(
101 hooks_patcher = mock.patch.object(
102 hooks_daemon.Hooks, rpc_method, create=True,
102 hooks_daemon.Hooks, rpc_method, create=True,
103 return_value=hook_result)
103 return_value=hook_result)
104
104
105 with read_patcher, hooks_patcher:
105 with read_patcher, hooks_patcher:
106 handler = hooks_daemon.HooksHttpHandler
106 handler = hooks_daemon.HooksHttpHandler
107 handler.DEFAULT_HOOKS_PROTO = test_proto
107 handler.DEFAULT_HOOKS_PROTO = test_proto
108 handler.wbufsize = 10240
108 handler.wbufsize = 10240
109 server = MockServer(handler, request)
109 server = MockServer(handler, request)
110
110
111 expected_result = hooks_daemon.HooksHttpHandler.serialize_data(hook_result)
111 expected_result = hooks_daemon.HooksHttpHandler.serialize_data(hook_result)
112
112
113 server.request.output_stream.seek(0)
113 server.request.output_stream.seek(0)
114 assert server.request.output_stream.readlines()[-1] == expected_result
114 assert server.request.output_stream.readlines()[-1] == expected_result
115
115
116 def test_exception_is_returned_in_response(self):
116 def test_exception_is_returned_in_response(self):
117 request = self._generate_post_request({})
117 request = self._generate_post_request({})
118 rpc_method = 'test'
118 rpc_method = 'test'
119
119
120 read_patcher = mock.patch.object(
120 read_patcher = mock.patch.object(
121 hooks_daemon.HooksHttpHandler, '_read_request',
121 hooks_daemon.HooksHttpHandler, '_read_request',
122 return_value=(test_proto, rpc_method, {}))
122 return_value=(test_proto, rpc_method, {}))
123
123
124 hooks_patcher = mock.patch.object(
124 hooks_patcher = mock.patch.object(
125 hooks_daemon.Hooks, rpc_method, create=True,
125 hooks_daemon.Hooks, rpc_method, create=True,
126 side_effect=Exception('Test exception'))
126 side_effect=Exception('Test exception'))
127
127
128 with read_patcher, hooks_patcher:
128 with read_patcher, hooks_patcher:
129 handler = hooks_daemon.HooksHttpHandler
129 handler = hooks_daemon.HooksHttpHandler
130 handler.DEFAULT_HOOKS_PROTO = test_proto
130 handler.DEFAULT_HOOKS_PROTO = test_proto
131 handler.wbufsize = 10240
131 handler.wbufsize = 10240
132 server = MockServer(handler, request)
132 server = MockServer(handler, request)
133
133
134 server.request.output_stream.seek(0)
134 server.request.output_stream.seek(0)
135 data = server.request.output_stream.readlines()
135 data = server.request.output_stream.readlines()
136 msgpack_data = b''.join(data[5:])
136 msgpack_data = b''.join(data[5:])
137 org_exc = hooks_daemon.HooksHttpHandler.deserialize_data(msgpack_data)
137 org_exc = hooks_daemon.HooksHttpHandler.deserialize_data(msgpack_data)
138 expected_result = {
138 expected_result = {
139 'exception': 'Exception',
139 'exception': 'Exception',
140 'exception_traceback': org_exc['exception_traceback'],
140 'exception_traceback': org_exc['exception_traceback'],
141 'exception_args': ['Test exception']
141 'exception_args': ['Test exception']
142 }
142 }
143 assert org_exc == expected_result
143 assert org_exc == expected_result
144
144
145 def test_log_message_writes_to_debug_log(self, caplog):
145 def test_log_message_writes_to_debug_log(self, caplog):
146 ip_port = ('0.0.0.0', 8888)
146 ip_port = ('0.0.0.0', 8888)
147 handler = hooks_daemon.HooksHttpHandler(
147 handler = hooks_daemon.HooksHttpHandler(
148 MockRequest('POST /'), ip_port, mock.Mock())
148 MockRequest('POST /'), ip_port, mock.Mock())
149 fake_date = '1/Nov/2015 00:00:00'
149 fake_date = '1/Nov/2015 00:00:00'
150 date_patcher = mock.patch.object(
150 date_patcher = mock.patch.object(
151 handler, 'log_date_time_string', return_value=fake_date)
151 handler, 'log_date_time_string', return_value=fake_date)
152
152
153 with date_patcher, caplog.at_level(logging.DEBUG):
153 with date_patcher, caplog.at_level(logging.DEBUG):
154 handler.log_message('Some message %d, %s', 123, 'string')
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 assert_message_in_log(
158 assert_message_in_log(
158 caplog.records, expected_message,
159 caplog.records, expected_message,
159 levelno=logging.DEBUG, module='hooks_daemon')
160 levelno=logging.DEBUG, module='hooks_daemon')
160
161
161 def _generate_post_request(self, data, proto=test_proto):
162 def _generate_post_request(self, data, proto=test_proto):
162 if proto == hooks_daemon.HooksHttpHandler.MSGPACK_HOOKS_PROTO:
163 if proto == hooks_daemon.HooksHttpHandler.MSGPACK_HOOKS_PROTO:
163 payload = msgpack.packb(data)
164 payload = msgpack.packb(data)
164 else:
165 else:
165 payload = json.dumps(data)
166 payload = json.dumps(data)
166
167
167 return b'POST / HTTP/1.0\nContent-Length: %d\n\n%b' % (
168 return b'POST / HTTP/1.0\nContent-Length: %d\n\n%b' % (
168 len(payload), payload)
169 len(payload), payload)
169
170
170
171
171 class ThreadedHookCallbackDaemon(object):
172 class ThreadedHookCallbackDaemon(object):
172 def test_constructor_calls_prepare(self):
173 def test_constructor_calls_prepare(self):
173 prepare_daemon_patcher = mock.patch.object(
174 prepare_daemon_patcher = mock.patch.object(
174 hooks_daemon.ThreadedHookCallbackDaemon, '_prepare')
175 hooks_daemon.ThreadedHookCallbackDaemon, '_prepare')
175 with prepare_daemon_patcher as prepare_daemon_mock:
176 with prepare_daemon_patcher as prepare_daemon_mock:
176 hooks_daemon.ThreadedHookCallbackDaemon()
177 hooks_daemon.ThreadedHookCallbackDaemon()
177 prepare_daemon_mock.assert_called_once_with()
178 prepare_daemon_mock.assert_called_once_with()
178
179
179 def test_run_is_called_on_context_start(self):
180 def test_run_is_called_on_context_start(self):
180 patchers = mock.patch.multiple(
181 patchers = mock.patch.multiple(
181 hooks_daemon.ThreadedHookCallbackDaemon,
182 hooks_daemon.ThreadedHookCallbackDaemon,
182 _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT)
183 _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT)
183
184
184 with patchers as mocks:
185 with patchers as mocks:
185 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
186 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
186 with daemon as daemon_context:
187 with daemon as daemon_context:
187 pass
188 pass
188 mocks['_run'].assert_called_once_with()
189 mocks['_run'].assert_called_once_with()
189 assert daemon_context == daemon
190 assert daemon_context == daemon
190
191
191 def test_stop_is_called_on_context_exit(self):
192 def test_stop_is_called_on_context_exit(self):
192 patchers = mock.patch.multiple(
193 patchers = mock.patch.multiple(
193 hooks_daemon.ThreadedHookCallbackDaemon,
194 hooks_daemon.ThreadedHookCallbackDaemon,
194 _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT)
195 _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT)
195
196
196 with patchers as mocks:
197 with patchers as mocks:
197 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
198 daemon = hooks_daemon.ThreadedHookCallbackDaemon()
198 with daemon as daemon_context:
199 with daemon as daemon_context:
199 assert mocks['_stop'].call_count == 0
200 assert mocks['_stop'].call_count == 0
200
201
201 mocks['_stop'].assert_called_once_with()
202 mocks['_stop'].assert_called_once_with()
202 assert daemon_context == daemon
203 assert daemon_context == daemon
203
204
204
205
205 class TestHttpHooksCallbackDaemon(object):
206 class TestHttpHooksCallbackDaemon(object):
206 def test_hooks_callback_generates_new_port(self, caplog):
207 def test_hooks_callback_generates_new_port(self, caplog):
207 with caplog.at_level(logging.DEBUG):
208 with caplog.at_level(logging.DEBUG):
208 daemon = hooks_daemon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881)
209 daemon = hooks_daemon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881)
209 assert daemon._daemon.server_address == ('127.0.0.1', 8881)
210 assert daemon._daemon.server_address == ('127.0.0.1', 8881)
210
211
211 with caplog.at_level(logging.DEBUG):
212 with caplog.at_level(logging.DEBUG):
212 daemon = hooks_daemon.HttpHooksCallbackDaemon(host=None, port=None)
213 daemon = hooks_daemon.HttpHooksCallbackDaemon(host=None, port=None)
213 assert daemon._daemon.server_address[1] in range(0, 66000)
214 assert daemon._daemon.server_address[1] in range(0, 66000)
214 assert daemon._daemon.server_address[0] != '127.0.0.1'
215 assert daemon._daemon.server_address[0] != '127.0.0.1'
215
216
216 def test_prepare_inits_daemon_variable(self, tcp_server, caplog):
217 def test_prepare_inits_daemon_variable(self, tcp_server, caplog):
217 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
218 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
218 daemon = hooks_daemon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881)
219 daemon = hooks_daemon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881)
219 assert daemon._daemon == tcp_server
220 assert daemon._daemon == tcp_server
220
221
221 _, port = tcp_server.server_address
222 _, port = tcp_server.server_address
222
223
223 msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \
224 msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \
224 f"hook object: <class 'rhodecode.lib.hooks_daemon.HooksHttpHandler'>"
225 f"hook object: <class 'rhodecode.lib.hooks_daemon.HooksHttpHandler'>"
225 assert_message_in_log(
226 assert_message_in_log(
226 caplog.records, msg, levelno=logging.DEBUG, module='hooks_daemon')
227 caplog.records, msg, levelno=logging.DEBUG, module='hooks_daemon')
227
228
228 def test_prepare_inits_hooks_uri_and_logs_it(
229 def test_prepare_inits_hooks_uri_and_logs_it(
229 self, tcp_server, caplog):
230 self, tcp_server, caplog):
230 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
231 with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG):
231 daemon = hooks_daemon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881)
232 daemon = hooks_daemon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881)
232
233
233 _, port = tcp_server.server_address
234 _, port = tcp_server.server_address
234 expected_uri = '{}:{}'.format('127.0.0.1', port)
235 expected_uri = '{}:{}'.format('127.0.0.1', port)
235 assert daemon.hooks_uri == expected_uri
236 assert daemon.hooks_uri == expected_uri
236
237
237 msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \
238 msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \
238 f"hook object: <class 'rhodecode.lib.hooks_daemon.HooksHttpHandler'>"
239 f"hook object: <class 'rhodecode.lib.hooks_daemon.HooksHttpHandler'>"
239 assert_message_in_log(
240 assert_message_in_log(
240 caplog.records, msg,
241 caplog.records, msg,
241 levelno=logging.DEBUG, module='hooks_daemon')
242 levelno=logging.DEBUG, module='hooks_daemon')
242
243
243 def test_run_creates_a_thread(self, tcp_server):
244 def test_run_creates_a_thread(self, tcp_server):
244 thread = mock.Mock()
245 thread = mock.Mock()
245
246
246 with self._tcp_patcher(tcp_server):
247 with self._tcp_patcher(tcp_server):
247 daemon = hooks_daemon.HttpHooksCallbackDaemon()
248 daemon = hooks_daemon.HttpHooksCallbackDaemon()
248
249
249 with self._thread_patcher(thread) as thread_mock:
250 with self._thread_patcher(thread) as thread_mock:
250 daemon._run()
251 daemon._run()
251
252
252 thread_mock.assert_called_once_with(
253 thread_mock.assert_called_once_with(
253 target=tcp_server.serve_forever,
254 target=tcp_server.serve_forever,
254 kwargs={'poll_interval': daemon.POLL_INTERVAL})
255 kwargs={'poll_interval': daemon.POLL_INTERVAL})
255 assert thread.daemon is True
256 assert thread.daemon is True
256 thread.start.assert_called_once_with()
257 thread.start.assert_called_once_with()
257
258
258 def test_run_logs(self, tcp_server, caplog):
259 def test_run_logs(self, tcp_server, caplog):
259
260
260 with self._tcp_patcher(tcp_server):
261 with self._tcp_patcher(tcp_server):
261 daemon = hooks_daemon.HttpHooksCallbackDaemon()
262 daemon = hooks_daemon.HttpHooksCallbackDaemon()
262
263
263 with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG):
264 with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG):
264 daemon._run()
265 daemon._run()
265
266
266 assert_message_in_log(
267 assert_message_in_log(
267 caplog.records,
268 caplog.records,
268 'Running event loop of callback daemon in background thread',
269 'Running thread-based loop of callback daemon in background',
269 levelno=logging.DEBUG, module='hooks_daemon')
270 levelno=logging.DEBUG, module='hooks_daemon')
270
271
271 def test_stop_cleans_up_the_connection(self, tcp_server, caplog):
272 def test_stop_cleans_up_the_connection(self, tcp_server, caplog):
272 thread = mock.Mock()
273 thread = mock.Mock()
273
274
274 with self._tcp_patcher(tcp_server):
275 with self._tcp_patcher(tcp_server):
275 daemon = hooks_daemon.HttpHooksCallbackDaemon()
276 daemon = hooks_daemon.HttpHooksCallbackDaemon()
276
277
277 with self._thread_patcher(thread), caplog.at_level(logging.DEBUG):
278 with self._thread_patcher(thread), caplog.at_level(logging.DEBUG):
278 with daemon:
279 with daemon:
279 assert daemon._daemon == tcp_server
280 assert daemon._daemon == tcp_server
280 assert daemon._callback_thread == thread
281 assert daemon._callback_thread == thread
281
282
282 assert daemon._daemon is None
283 assert daemon._daemon is None
283 assert daemon._callback_thread is None
284 assert daemon._callback_thread is None
284 tcp_server.shutdown.assert_called_with()
285 tcp_server.shutdown.assert_called_with()
285 thread.join.assert_called_once_with()
286 thread.join.assert_called_once_with()
286
287
287 assert_message_in_log(
288 assert_message_in_log(
288 caplog.records, 'Waiting for background thread to finish.',
289 caplog.records, 'Waiting for background thread to finish.',
289 levelno=logging.DEBUG, module='hooks_daemon')
290 levelno=logging.DEBUG, module='hooks_daemon')
290
291
291 def _tcp_patcher(self, tcp_server):
292 def _tcp_patcher(self, tcp_server):
292 return mock.patch.object(
293 return mock.patch.object(
293 hooks_daemon, 'TCPServer', return_value=tcp_server)
294 hooks_daemon, 'TCPServer', return_value=tcp_server)
294
295
295 def _thread_patcher(self, thread):
296 def _thread_patcher(self, thread):
296 return mock.patch.object(
297 return mock.patch.object(
297 hooks_daemon.threading, 'Thread', return_value=thread)
298 hooks_daemon.threading, 'Thread', return_value=thread)
298
299
299
300
300 class TestPrepareHooksDaemon(object):
301 class TestPrepareHooksDaemon(object):
301 @pytest.mark.parametrize('protocol', ('http',))
302 @pytest.mark.parametrize('protocol', ('http',))
302 def test_returns_dummy_hooks_callback_daemon_when_using_direct_calls(
303 def test_returns_dummy_hooks_callback_daemon_when_using_direct_calls(
303 self, protocol):
304 self, protocol):
304 expected_extras = {'extra1': 'value1'}
305 expected_extras = {'extra1': 'value1'}
305 callback, extras = hooks_daemon.prepare_callback_daemon(
306 callback, extras = hooks_daemon.prepare_callback_daemon(
306 expected_extras.copy(), protocol=protocol,
307 expected_extras.copy(), protocol=protocol,
307 host='127.0.0.1', use_direct_calls=True)
308 host='127.0.0.1', use_direct_calls=True)
308 assert isinstance(callback, hooks_daemon.DummyHooksCallbackDaemon)
309 assert isinstance(callback, hooks_daemon.DummyHooksCallbackDaemon)
309 expected_extras['hooks_module'] = 'rhodecode.lib.hooks_daemon'
310 expected_extras['hooks_module'] = 'rhodecode.lib.hooks_daemon'
310 expected_extras['time'] = extras['time']
311 expected_extras['time'] = extras['time']
311 assert 'extra1' in extras
312 assert 'extra1' in extras
312
313
313 @pytest.mark.parametrize('protocol, expected_class', (
314 @pytest.mark.parametrize('protocol, expected_class', (
314 ('http', hooks_daemon.HttpHooksCallbackDaemon),
315 ('http', hooks_daemon.HttpHooksCallbackDaemon),
315 ))
316 ))
316 def test_returns_real_hooks_callback_daemon_when_protocol_is_specified(
317 def test_returns_real_hooks_callback_daemon_when_protocol_is_specified(
317 self, protocol, expected_class):
318 self, protocol, expected_class):
318 expected_extras = {
319 expected_extras = {
319 'extra1': 'value1',
320 'extra1': 'value1',
320 'txn_id': 'txnid2',
321 'txn_id': 'txnid2',
321 'hooks_protocol': protocol.lower()
322 'hooks_protocol': protocol.lower()
322 }
323 }
323 callback, extras = hooks_daemon.prepare_callback_daemon(
324 callback, extras = hooks_daemon.prepare_callback_daemon(
324 expected_extras.copy(), protocol=protocol, host='127.0.0.1',
325 expected_extras.copy(), protocol=protocol, host='127.0.0.1',
325 use_direct_calls=False,
326 use_direct_calls=False,
326 txn_id='txnid2')
327 txn_id='txnid2')
327 assert isinstance(callback, expected_class)
328 assert isinstance(callback, expected_class)
328 extras.pop('hooks_uri')
329 extras.pop('hooks_uri')
329 expected_extras['time'] = extras['time']
330 expected_extras['time'] = extras['time']
330 assert extras == expected_extras
331 assert extras == expected_extras
331
332
332 @pytest.mark.parametrize('protocol', (
333 @pytest.mark.parametrize('protocol', (
333 'invalid',
334 'invalid',
334 'Http',
335 'Http',
335 'HTTP',
336 'HTTP',
336 ))
337 ))
337 def test_raises_on_invalid_protocol(self, protocol):
338 def test_raises_on_invalid_protocol(self, protocol):
338 expected_extras = {
339 expected_extras = {
339 'extra1': 'value1',
340 'extra1': 'value1',
340 'hooks_protocol': protocol.lower()
341 'hooks_protocol': protocol.lower()
341 }
342 }
342 with pytest.raises(Exception):
343 with pytest.raises(Exception):
343 callback, extras = hooks_daemon.prepare_callback_daemon(
344 callback, extras = hooks_daemon.prepare_callback_daemon(
344 expected_extras.copy(),
345 expected_extras.copy(),
345 protocol=protocol, host='127.0.0.1',
346 protocol=protocol, host='127.0.0.1',
346 use_direct_calls=False)
347 use_direct_calls=False)
347
348
348
349
349 class MockRequest(object):
350 class MockRequest(object):
350
351
351 def __init__(self, request):
352 def __init__(self, request):
352 self.request = request
353 self.request = request
353 self.input_stream = io.BytesIO(safe_bytes(self.request))
354 self.input_stream = io.BytesIO(safe_bytes(self.request))
354 self.output_stream = io.BytesIO() # make it un-closable for testing invesitagion
355 self.output_stream = io.BytesIO() # make it un-closable for testing invesitagion
355 self.output_stream.close = lambda: None
356 self.output_stream.close = lambda: None
356
357
357 def makefile(self, mode, *args, **kwargs):
358 def makefile(self, mode, *args, **kwargs):
358 return self.output_stream if mode == 'wb' else self.input_stream
359 return self.output_stream if mode == 'wb' else self.input_stream
359
360
360
361
361 class MockServer(object):
362 class MockServer(object):
362
363
363 def __init__(self, handler_cls, request):
364 def __init__(self, handler_cls, request):
364 ip_port = ('0.0.0.0', 8888)
365 ip_port = ('0.0.0.0', 8888)
365 self.request = MockRequest(request)
366 self.request = MockRequest(request)
366 self.server_address = ip_port
367 self.server_address = ip_port
367 self.handler = handler_cls(self.request, ip_port, self)
368 self.handler = handler_cls(self.request, ip_port, self)
368
369
369
370
370 @pytest.fixture()
371 @pytest.fixture()
371 def tcp_server():
372 def tcp_server():
372 server = mock.Mock()
373 server = mock.Mock()
373 server.server_address = ('127.0.0.1', 8881)
374 server.server_address = ('127.0.0.1', 8881)
374 server.wbufsize = 1024
375 server.wbufsize = 1024
375 return server
376 return server
General Comments 0
You need to be logged in to leave comments. Login now