248 lines
11 KiB
Python
248 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
proxy.py
|
|
~~~~~~~~
|
|
⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
|
|
Network monitoring, controls & Application development, testing, debugging.
|
|
|
|
:copyright: (c) 2013-present by Abhinav Singh and contributors.
|
|
:license: BSD, see LICENSE for more details.
|
|
"""
|
|
import unittest
|
|
|
|
from unittest import mock
|
|
|
|
from proxy.proxy import main, entry_point
|
|
from proxy.common.utils import bytes_
|
|
|
|
from proxy.common.constants import DEFAULT_ENABLE_DASHBOARD, DEFAULT_LOG_LEVEL, DEFAULT_LOG_FILE, DEFAULT_LOG_FORMAT
|
|
from proxy.common.constants import DEFAULT_TIMEOUT, DEFAULT_DEVTOOLS_WS_PATH, DEFAULT_DISABLE_HTTP_PROXY
|
|
from proxy.common.constants import DEFAULT_ENABLE_STATIC_SERVER, DEFAULT_ENABLE_EVENTS, DEFAULT_ENABLE_DEVTOOLS
|
|
from proxy.common.constants import DEFAULT_ENABLE_WEB_SERVER, DEFAULT_THREADLESS, DEFAULT_CERT_FILE, DEFAULT_KEY_FILE
|
|
from proxy.common.constants import DEFAULT_CA_CERT_FILE, DEFAULT_CA_KEY_FILE, DEFAULT_CA_SIGNING_KEY_FILE
|
|
from proxy.common.constants import DEFAULT_PAC_FILE, DEFAULT_PLUGINS, DEFAULT_PID_FILE, DEFAULT_PORT, DEFAULT_BASIC_AUTH
|
|
from proxy.common.constants import DEFAULT_NUM_WORKERS, DEFAULT_OPEN_FILE_LIMIT, DEFAULT_IPV6_HOSTNAME
|
|
from proxy.common.constants import DEFAULT_SERVER_RECVBUF_SIZE, DEFAULT_CLIENT_RECVBUF_SIZE, DEFAULT_WORK_KLASS
|
|
from proxy.common.constants import PLUGIN_INSPECT_TRAFFIC, PLUGIN_DASHBOARD, PLUGIN_DEVTOOLS_PROTOCOL, PLUGIN_WEB_SERVER
|
|
from proxy.common.constants import PLUGIN_HTTP_PROXY, DEFAULT_NUM_ACCEPTORS
|
|
|
|
|
|
class TestMain(unittest.TestCase):
|
|
|
|
@staticmethod
|
|
def mock_default_args(mock_args: mock.Mock) -> None:
|
|
"""Use when trying to mock parse_args"""
|
|
mock_args.version = False
|
|
mock_args.cert_file = DEFAULT_CERT_FILE
|
|
mock_args.key_file = DEFAULT_KEY_FILE
|
|
mock_args.ca_key_file = DEFAULT_CA_KEY_FILE
|
|
mock_args.ca_cert_file = DEFAULT_CA_CERT_FILE
|
|
mock_args.ca_signing_key_file = DEFAULT_CA_SIGNING_KEY_FILE
|
|
mock_args.pid_file = DEFAULT_PID_FILE
|
|
mock_args.log_file = DEFAULT_LOG_FILE
|
|
mock_args.log_level = DEFAULT_LOG_LEVEL
|
|
mock_args.log_format = DEFAULT_LOG_FORMAT
|
|
mock_args.basic_auth = DEFAULT_BASIC_AUTH
|
|
mock_args.hostname = DEFAULT_IPV6_HOSTNAME
|
|
mock_args.port = DEFAULT_PORT
|
|
mock_args.num_acceptors = DEFAULT_NUM_ACCEPTORS
|
|
mock_args.num_workers = DEFAULT_NUM_WORKERS
|
|
mock_args.disable_http_proxy = DEFAULT_DISABLE_HTTP_PROXY
|
|
mock_args.pac_file = DEFAULT_PAC_FILE
|
|
mock_args.plugins = DEFAULT_PLUGINS
|
|
mock_args.server_recvbuf_size = DEFAULT_SERVER_RECVBUF_SIZE
|
|
mock_args.client_recvbuf_size = DEFAULT_CLIENT_RECVBUF_SIZE
|
|
mock_args.open_file_limit = DEFAULT_OPEN_FILE_LIMIT
|
|
mock_args.devtools_event_queue = None
|
|
mock_args.devtools_ws_path = DEFAULT_DEVTOOLS_WS_PATH
|
|
mock_args.timeout = DEFAULT_TIMEOUT
|
|
mock_args.threadless = DEFAULT_THREADLESS
|
|
mock_args.threaded = not DEFAULT_THREADLESS
|
|
mock_args.enable_web_server = DEFAULT_ENABLE_WEB_SERVER
|
|
mock_args.enable_static_server = DEFAULT_ENABLE_STATIC_SERVER
|
|
mock_args.enable_devtools = DEFAULT_ENABLE_DEVTOOLS
|
|
mock_args.enable_events = DEFAULT_ENABLE_EVENTS
|
|
mock_args.enable_dashboard = DEFAULT_ENABLE_DASHBOARD
|
|
mock_args.work_klass = DEFAULT_WORK_KLASS
|
|
|
|
@mock.patch('time.sleep')
|
|
@mock.patch('proxy.proxy.FlagParser.initialize')
|
|
@mock.patch('proxy.proxy.EventManager')
|
|
@mock.patch('proxy.proxy.AcceptorPool')
|
|
@mock.patch('proxy.proxy.ThreadlessPool')
|
|
def test_entry_point(
|
|
self,
|
|
mock_executor_pool: mock.Mock,
|
|
mock_acceptor_pool: mock.Mock,
|
|
mock_event_manager: mock.Mock,
|
|
mock_initialize: mock.Mock,
|
|
mock_sleep: mock.Mock,
|
|
) -> None:
|
|
mock_sleep.side_effect = KeyboardInterrupt()
|
|
mock_initialize.return_value.enable_events = False
|
|
entry_point()
|
|
mock_event_manager.assert_not_called()
|
|
mock_executor_pool.assert_called_once_with(
|
|
flags=mock_initialize.return_value,
|
|
event_queue=None,
|
|
)
|
|
mock_acceptor_pool.assert_called_once_with(
|
|
flags=mock_initialize.return_value,
|
|
event_queue=None,
|
|
executor_queues=mock_executor_pool.return_value.work_queues,
|
|
executor_pids=mock_executor_pool.return_value.work_pids,
|
|
)
|
|
mock_acceptor_pool.return_value.setup.assert_called()
|
|
mock_acceptor_pool.return_value.shutdown.assert_called()
|
|
mock_sleep.assert_called()
|
|
|
|
@mock.patch('time.sleep')
|
|
@mock.patch('proxy.proxy.FlagParser.initialize')
|
|
@mock.patch('proxy.proxy.EventManager')
|
|
@mock.patch('proxy.proxy.AcceptorPool')
|
|
@mock.patch('proxy.proxy.ThreadlessPool')
|
|
def test_main_with_no_flags(
|
|
self,
|
|
mock_executor_pool: mock.Mock,
|
|
mock_acceptor_pool: mock.Mock,
|
|
mock_event_manager: mock.Mock,
|
|
mock_initialize: mock.Mock,
|
|
mock_sleep: mock.Mock,
|
|
) -> None:
|
|
mock_sleep.side_effect = KeyboardInterrupt()
|
|
mock_initialize.return_value.enable_events = False
|
|
main()
|
|
mock_event_manager.assert_not_called()
|
|
mock_executor_pool.assert_called_once_with(
|
|
flags=mock_initialize.return_value,
|
|
event_queue=None,
|
|
)
|
|
mock_acceptor_pool.assert_called_once_with(
|
|
flags=mock_initialize.return_value,
|
|
event_queue=None,
|
|
executor_queues=mock_executor_pool.return_value.work_queues,
|
|
executor_pids=mock_executor_pool.return_value.work_pids,
|
|
)
|
|
mock_acceptor_pool.return_value.setup.assert_called()
|
|
mock_acceptor_pool.return_value.shutdown.assert_called()
|
|
mock_sleep.assert_called()
|
|
|
|
@mock.patch('time.sleep')
|
|
@mock.patch('proxy.proxy.FlagParser.initialize')
|
|
@mock.patch('proxy.proxy.EventManager')
|
|
@mock.patch('proxy.proxy.AcceptorPool')
|
|
@mock.patch('proxy.proxy.ThreadlessPool')
|
|
def test_enable_events(
|
|
self,
|
|
mock_executor_pool: mock.Mock,
|
|
mock_acceptor_pool: mock.Mock,
|
|
mock_event_manager: mock.Mock,
|
|
mock_initialize: mock.Mock,
|
|
mock_sleep: mock.Mock,
|
|
) -> None:
|
|
mock_sleep.side_effect = KeyboardInterrupt()
|
|
mock_initialize.return_value.enable_events = True
|
|
main()
|
|
mock_event_manager.assert_called_once()
|
|
mock_event_manager.return_value.setup.assert_called_once()
|
|
mock_event_manager.return_value.shutdown.assert_called_once()
|
|
mock_executor_pool.assert_called_once_with(
|
|
flags=mock_initialize.return_value,
|
|
event_queue=mock_event_manager.return_value.queue,
|
|
)
|
|
mock_acceptor_pool.assert_called_once_with(
|
|
flags=mock_initialize.return_value,
|
|
event_queue=mock_event_manager.return_value.queue,
|
|
executor_queues=mock_executor_pool.return_value.work_queues,
|
|
executor_pids=mock_executor_pool.return_value.work_pids,
|
|
)
|
|
mock_acceptor_pool.return_value.setup.assert_called()
|
|
mock_acceptor_pool.return_value.shutdown.assert_called()
|
|
mock_sleep.assert_called()
|
|
|
|
@mock.patch('time.sleep')
|
|
@mock.patch('proxy.common.plugins.Plugins.load')
|
|
@mock.patch('proxy.common.flag.FlagParser.parse_args')
|
|
@mock.patch('proxy.proxy.EventManager')
|
|
@mock.patch('proxy.proxy.AcceptorPool')
|
|
@mock.patch('proxy.proxy.ThreadlessPool')
|
|
def test_enable_dashboard(
|
|
self,
|
|
mock_executor_pool: mock.Mock,
|
|
mock_acceptor_pool: mock.Mock,
|
|
mock_event_manager: mock.Mock,
|
|
mock_parse_args: mock.Mock,
|
|
mock_load_plugins: mock.Mock,
|
|
mock_sleep: mock.Mock,
|
|
) -> None:
|
|
mock_sleep.side_effect = KeyboardInterrupt()
|
|
mock_args = mock_parse_args.return_value
|
|
self.mock_default_args(mock_args)
|
|
mock_args.enable_dashboard = True
|
|
main(enable_dashboard=True)
|
|
mock_load_plugins.assert_called()
|
|
self.assertEqual(
|
|
mock_load_plugins.call_args_list[0][0][0], [
|
|
bytes_(PLUGIN_WEB_SERVER),
|
|
bytes_(PLUGIN_DASHBOARD),
|
|
bytes_(PLUGIN_INSPECT_TRAFFIC),
|
|
bytes_(PLUGIN_DEVTOOLS_PROTOCOL),
|
|
bytes_(PLUGIN_HTTP_PROXY),
|
|
],
|
|
)
|
|
# TODO: Assert arguments passed to parse_arg
|
|
mock_parse_args.assert_called_once()
|
|
# dashboard will also enable eventing
|
|
mock_event_manager.assert_called_once()
|
|
mock_event_manager.return_value.setup.assert_called_once()
|
|
mock_event_manager.return_value.shutdown.assert_called_once()
|
|
mock_executor_pool.assert_called_once()
|
|
mock_executor_pool.return_value.setup.assert_called_once()
|
|
mock_acceptor_pool.assert_called_once()
|
|
mock_acceptor_pool.return_value.setup.assert_called_once()
|
|
|
|
@mock.patch('time.sleep')
|
|
@mock.patch('proxy.common.plugins.Plugins.load')
|
|
@mock.patch('proxy.common.flag.FlagParser.parse_args')
|
|
@mock.patch('proxy.proxy.EventManager')
|
|
@mock.patch('proxy.proxy.AcceptorPool')
|
|
@mock.patch('proxy.proxy.ThreadlessPool')
|
|
def test_enable_devtools(
|
|
self,
|
|
mock_executor_pool: mock.Mock,
|
|
mock_acceptor_pool: mock.Mock,
|
|
mock_event_manager: mock.Mock,
|
|
mock_parse_args: mock.Mock,
|
|
mock_load_plugins: mock.Mock,
|
|
mock_sleep: mock.Mock,
|
|
) -> None:
|
|
mock_sleep.side_effect = KeyboardInterrupt()
|
|
mock_args = mock_parse_args.return_value
|
|
self.mock_default_args(mock_args)
|
|
mock_args.enable_devtools = True
|
|
main(enable_devtools=True)
|
|
mock_load_plugins.assert_called()
|
|
self.assertEqual(
|
|
mock_load_plugins.call_args_list[0][0][0], [
|
|
bytes_(PLUGIN_DEVTOOLS_PROTOCOL),
|
|
bytes_(PLUGIN_WEB_SERVER),
|
|
bytes_(PLUGIN_HTTP_PROXY),
|
|
],
|
|
)
|
|
mock_parse_args.assert_called_once()
|
|
# Currently --enable-devtools flag alone doesn't enable eventing core
|
|
mock_event_manager.assert_not_called()
|
|
mock_executor_pool.assert_called_once()
|
|
mock_executor_pool.return_value.setup.assert_called_once()
|
|
mock_acceptor_pool.assert_called_once()
|
|
mock_acceptor_pool.return_value.setup.assert_called_once()
|
|
|
|
# def test_pac_file(self) -> None:
|
|
# pass
|
|
|
|
# def test_imports_plugin(self) -> None:
|
|
# pass
|
|
|
|
# def test_cannot_enable_https_proxy_and_tls_interception_mutually(self) -> None:
|
|
# pass
|