class PreviousCommandUnitTest(unittest.TestCase):
    def setUp(self) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.client_config = ClientConfigMap()
        self.config_adapter = ClientConfigAdapter(self.client_config)

        self.app = HummingbotApplication(self.config_adapter)
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        super().tearDown()

    def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1):
        ret = self.ev_loop.run_until_complete(
            asyncio.wait_for(coroutine, timeout))
        return ret

    def mock_user_response(self, config):
        config.value = "yes"

    def test_no_previous_strategy_found(self):
        self.config_adapter.previous_strategy = None
        self.app.previous_strategy(option="")
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                "No previous strategy found."))

    @patch(
        "hummingbot.client.command.import_command.ImportCommand.import_command"
    )
    def test_strategy_found_and_user_declines(self, import_command: MagicMock):
        strategy_name = "conf_1.yml"
        self.cli_mock_assistant.queue_prompt_reply("No")
        self.async_run_with_timeout(
            self.app.prompt_for_previous_strategy(strategy_name))
        import_command.assert_not_called()

    @patch(
        "hummingbot.client.command.import_command.ImportCommand.import_command"
    )
    def test_strategy_found_and_user_accepts(self, import_command: MagicMock):
        strategy_name = "conf_1.yml"
        self.config_adapter.previous_strategy = strategy_name
        self.cli_mock_assistant.queue_prompt_reply("Yes")
        self.async_run_with_timeout(
            self.app.prompt_for_previous_strategy(strategy_name))
        import_command.assert_called()
        self.assertTrue(import_command.call_args[0][1] == strategy_name)
class CreateCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())
        self.client_config_map = ClientConfigAdapter(ClientConfigMap())

        self.app = HummingbotApplication(
            client_config_map=self.client_config_map)
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        super().tearDown()

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)

        return async_sleep

    def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1):
        ret = self.ev_loop.run_until_complete(
            asyncio.wait_for(coroutine, timeout))
        return ret

    def async_run_with_timeout_coroutine_must_raise_timeout(
            self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine),
                                        timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @patch("shutil.copy")
    @patch("hummingbot.client.command.create_command.save_to_yml_legacy")
    @patch("hummingbot.client.config.security.Security.is_decryption_done")
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.validate_required_connections"
    )
    @patch("hummingbot.core.utils.market_price.get_last_price")
    def test_prompt_for_configuration_re_prompts_on_lower_than_minimum_amount(
        self,
        get_last_price_mock: AsyncMock,
        validate_required_connections_mock: AsyncMock,
        is_decryption_done_mock: MagicMock,
        save_to_yml_mock: MagicMock,
        _: MagicMock,
    ):
        get_last_price_mock.return_value = Decimal("11")
        validate_required_connections_mock.return_value = {}
        is_decryption_done_mock.return_value = True
        config_maps = []
        save_to_yml_mock.side_effect = lambda _, cm: config_maps.append(cm)

        self.client_config_map.commands_timeout.create_command_timeout = 10
        self.client_config_map.commands_timeout.other_commands_timeout = 30
        strategy_name = "some-strategy"
        strategy_file_name = f"{strategy_name}.yml"
        base_strategy = "pure_market_making"
        self.cli_mock_assistant.queue_prompt_reply(base_strategy)  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_reply("BTC-USDT")  # trading pair
        self.cli_mock_assistant.queue_prompt_reply("1")  # bid spread
        self.cli_mock_assistant.queue_prompt_reply("1")  # ask spread
        self.cli_mock_assistant.queue_prompt_reply("30")  # order refresh time
        self.cli_mock_assistant.queue_prompt_reply(
            "0")  # unacceptable order amount
        self.cli_mock_assistant.queue_prompt_reply(
            "1")  # acceptable order amount
        self.cli_mock_assistant.queue_prompt_reply("No")  # ping pong feature

        self.async_run_with_timeout(
            self.app.prompt_for_configuration(strategy_file_name))
        self.assertEqual(strategy_file_name, self.app.strategy_file_name)
        self.assertEqual(base_strategy, self.app.strategy_name)
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg="Value must be more than 0."))

    @patch("shutil.copy")
    @patch("hummingbot.client.command.create_command.save_to_yml_legacy")
    @patch("hummingbot.client.config.security.Security.is_decryption_done")
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.validate_required_connections"
    )
    @patch("hummingbot.core.utils.market_price.get_last_price")
    def test_prompt_for_configuration_accepts_zero_amount_on_get_last_price_network_timeout(
        self,
        get_last_price_mock: AsyncMock,
        validate_required_connections_mock: AsyncMock,
        is_decryption_done_mock: MagicMock,
        save_to_yml_mock: MagicMock,
        _: MagicMock,
    ):
        get_last_price_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        validate_required_connections_mock.return_value = {}
        is_decryption_done_mock.return_value = True
        config_maps = []
        save_to_yml_mock.side_effect = lambda _, cm: config_maps.append(cm)

        self.client_config_map.commands_timeout.create_command_timeout = 0.005
        self.client_config_map.commands_timeout.other_commands_timeout = 0.01
        strategy_name = "some-strategy"
        strategy_file_name = f"{strategy_name}.yml"
        base_strategy = "pure_market_making"
        self.cli_mock_assistant.queue_prompt_reply(base_strategy)  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_reply("BTC-USDT")  # trading pair
        self.cli_mock_assistant.queue_prompt_reply("1")  # bid spread
        self.cli_mock_assistant.queue_prompt_reply("1")  # ask spread
        self.cli_mock_assistant.queue_prompt_reply("30")  # order refresh time
        self.cli_mock_assistant.queue_prompt_reply("1")  # order amount
        self.cli_mock_assistant.queue_prompt_reply("No")  # ping pong feature

        self.async_run_with_timeout(
            self.app.prompt_for_configuration(strategy_file_name))
        self.assertEqual(strategy_file_name, self.app.strategy_file_name)
        self.assertEqual(base_strategy, self.app.strategy_name)

    def test_create_command_restores_config_map_after_config_stop(self):
        base_strategy = "pure_market_making"
        strategy_config = get_strategy_config_map(base_strategy)
        original_exchange = "bybit"
        strategy_config["exchange"].value = original_exchange

        self.cli_mock_assistant.queue_prompt_reply(base_strategy)  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_to_stop_config(
        )  # cancel on trading pair prompt

        self.async_run_with_timeout(self.app.prompt_for_configuration(None))
        strategy_config = get_strategy_config_map(base_strategy)

        self.assertEqual(original_exchange, strategy_config["exchange"].value)

    def test_create_command_restores_config_map_after_config_stop_on_new_file_prompt(
            self):
        base_strategy = "pure_market_making"
        strategy_config = get_strategy_config_map(base_strategy)
        original_exchange = "bybit"
        strategy_config["exchange"].value = original_exchange

        self.cli_mock_assistant.queue_prompt_reply(base_strategy)  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_reply("BTC-USDT")  # trading pair
        self.cli_mock_assistant.queue_prompt_reply("1")  # bid spread
        self.cli_mock_assistant.queue_prompt_reply("1")  # ask spread
        self.cli_mock_assistant.queue_prompt_reply("30")  # order refresh time
        self.cli_mock_assistant.queue_prompt_reply("1")  # order amount
        self.cli_mock_assistant.queue_prompt_reply("No")  # ping pong feature
        self.cli_mock_assistant.queue_prompt_to_stop_config(
        )  # cancel on new file prompt

        self.async_run_with_timeout(self.app.prompt_for_configuration(None))
        strategy_config = get_strategy_config_map(base_strategy)

        self.assertEqual(original_exchange, strategy_config["exchange"].value)

    @patch("shutil.copy")
    @patch("hummingbot.client.command.create_command.save_to_yml_legacy")
    @patch("hummingbot.client.config.security.Security.is_decryption_done")
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.validate_required_connections"
    )
    @patch("hummingbot.core.utils.market_price.get_last_price")
    def test_prompt_for_configuration_handles_status_network_timeout(
        self,
        get_last_price_mock: AsyncMock,
        validate_required_connections_mock: AsyncMock,
        is_decryption_done_mock: MagicMock,
        _: MagicMock,
        __: MagicMock,
    ):
        get_last_price_mock.return_value = None
        validate_required_connections_mock.side_effect = self.get_async_sleep_fn(
            delay=0.02)
        is_decryption_done_mock.return_value = True
        self.client_config_map.commands_timeout.create_command_timeout = 0.005
        self.client_config_map.commands_timeout.other_commands_timeout = 0.01
        strategy_file_name = "some-strategy.yml"
        self.cli_mock_assistant.queue_prompt_reply(
            "pure_market_making")  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_reply("BTC-USDT")  # trading pair
        self.cli_mock_assistant.queue_prompt_reply("1")  # bid spread
        self.cli_mock_assistant.queue_prompt_reply("1")  # ask spread
        self.cli_mock_assistant.queue_prompt_reply("30")  # order refresh time
        self.cli_mock_assistant.queue_prompt_reply("1")  # order amount
        self.cli_mock_assistant.queue_prompt_reply("No")  # ping pong feature

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.prompt_for_configuration(strategy_file_name))
        self.assertEqual(None, self.app.strategy_file_name)
        self.assertEqual(None, self.app.strategy_name)
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection check to complete. See logs for more details."
            ))
예제 #3
0
class ConnectCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.app = HummingbotApplication()
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()
        self.global_config_backup = deepcopy(global_config_map)

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        self.reset_global_config()
        Security._decryption_done.clear()
        super().tearDown()

    def reset_global_config(self):
        for key, value in self.global_config_backup.items():
            global_config_map[key] = value

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)

        return async_sleep

    def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1):
        ret = self.ev_loop.run_until_complete(
            asyncio.wait_for(coroutine, timeout))
        return ret

    def async_run_with_timeout_coroutine_must_raise_timeout(
            self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine),
                                        timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @patch("hummingbot.client.config.security.Security.update_secure_config")
    @patch("hummingbot.client.config.security.Security.encrypted_file_exists")
    @patch("hummingbot.client.config.security.Security.api_keys")
    @patch("hummingbot.user.user_balances.UserBalances.add_exchange")
    def test_connect_exchange_success(
        self,
        add_exchange_mock: AsyncMock,
        api_keys_mock: AsyncMock,
        encrypted_file_exists_mock: MagicMock,
        _: MagicMock,
    ):
        add_exchange_mock.return_value = None
        exchange = "binance"
        api_key = "someKey"
        api_secret = "someSecret"
        api_keys_mock.return_value = {
            "binance_api_key": api_key,
            "binance_api_secret": api_secret
        }
        encrypted_file_exists_mock.return_value = False
        global_config_map["other_commands_timeout"].value = 30
        self.cli_mock_assistant.queue_prompt_reply(api_key)  # binance API key
        self.cli_mock_assistant.queue_prompt_reply(
            api_secret)  # binance API secret

        self.async_run_with_timeout(self.app.connect_exchange(exchange))
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=f"\nYou are now connected to {exchange}."))
        self.assertFalse(self.app.placeholder_mode)
        self.assertFalse(self.app.app.hide_input)

    @patch("hummingbot.client.config.security.Security.update_secure_config")
    @patch("hummingbot.client.config.security.Security.encrypted_file_exists")
    @patch("hummingbot.client.config.security.Security.api_keys")
    @patch("hummingbot.user.user_balances.UserBalances.add_exchange")
    def test_connect_exchange_handles_network_timeouts(
        self,
        add_exchange_mock: AsyncMock,
        api_keys_mock: AsyncMock,
        encrypted_file_exists_mock: MagicMock,
        _: MagicMock,
    ):
        add_exchange_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01
        api_key = "someKey"
        api_secret = "someSecret"
        api_keys_mock.return_value = {
            "binance_api_key": api_key,
            "binance_api_secret": api_secret
        }
        encrypted_file_exists_mock.return_value = False
        self.cli_mock_assistant.queue_prompt_reply(api_key)  # binance API key
        self.cli_mock_assistant.queue_prompt_reply(
            api_secret)  # binance API secret

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connect_exchange("binance"))
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection to complete. See logs for more details."
            ))
        self.assertFalse(self.app.placeholder_mode)
        self.assertFalse(self.app.app.hide_input)

    @patch("hummingbot.user.user_balances.UserBalances.update_exchanges")
    def test_connection_df_handles_network_timeouts(
            self, update_exchanges_mock: AsyncMock):
        update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connection_df())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection table to populate. See logs for more details."
            ))

    @patch("hummingbot.user.user_balances.UserBalances.update_exchanges")
    def test_connection_df_handles_network_timeouts_logs_hidden(
            self, update_exchanges_mock: AsyncMock):
        self.cli_mock_assistant.toggle_logs()

        update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connection_df())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection table to populate. See logs for more details."
            ))

    @patch(
        "hummingbot.client.hummingbot_application.HummingbotApplication._notify"
    )
    @patch(
        "hummingbot.client.hummingbot_application.HummingbotApplication.connection_df"
    )
    def test_show_connections(self, connection_df_mock, notify_mock):
        global_config_map["tables_format"].value = "psql"

        Security._decryption_done.set()

        captures = []
        notify_mock.side_effect = lambda s: captures.append(s)

        connections_df = pd.DataFrame(
            columns=pd.Index(
                ['Exchange', '  Keys Added', '  Keys Confirmed', '  Status'],
                dtype='object'),
            data=[["ascend_ex", "Yes", "Yes", "&cYELLOW"],
                  ["beaxy", "Yes", "Yes", "&cGREEN"]])
        connection_df_mock.return_value = (connections_df, [])

        self.async_run_with_timeout(self.app.show_connections())

        self.assertEqual(2, len(captures))
        self.assertEqual("\nTesting connections, please wait...", captures[0])

        df_str_expected = (
            "    +------------+----------------+--------------------+------------+"
            "\n    | Exchange   |   Keys Added   |   Keys Confirmed   |   Status   |"
            "\n    |------------+----------------+--------------------+------------|"
            "\n    | ascend_ex  | Yes            | Yes                | &cYELLOW   |"
            "\n    | beaxy      | Yes            | Yes                | &cGREEN    |"
            "\n    +------------+----------------+--------------------+------------+"
        )

        self.assertEqual(df_str_expected, captures[1])
예제 #4
0
class ConnectCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.app = HummingbotApplication()
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()
        self.global_config_backup = deepcopy(global_config_map)

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        self.reset_global_config()
        super().tearDown()

    def reset_global_config(self):
        for key, value in self.global_config_backup.items():
            global_config_map[key] = value

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)

        return async_sleep

    def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1):
        ret = self.ev_loop.run_until_complete(
            asyncio.wait_for(coroutine, timeout))
        return ret

    def async_run_with_timeout_coroutine_must_raise_timeout(
            self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine),
                                        timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @patch("hummingbot.client.config.security.Security.update_secure_config")
    @patch("hummingbot.client.config.security.Security.encrypted_file_exists")
    @patch("hummingbot.client.config.security.Security.api_keys")
    @patch("hummingbot.user.user_balances.UserBalances.add_exchange")
    def test_connect_exchange_success(
        self,
        add_exchange_mock: AsyncMock,
        api_keys_mock: AsyncMock,
        encrypted_file_exists_mock: MagicMock,
        _: MagicMock,
    ):
        add_exchange_mock.return_value = None
        exchange = "binance"
        api_key = "someKey"
        api_secret = "someSecret"
        api_keys_mock.return_value = {
            "binance_api_key": api_key,
            "binance_api_secret": api_secret
        }
        encrypted_file_exists_mock.return_value = False
        global_config_map["other_commands_timeout"].value = 30
        self.cli_mock_assistant.queue_prompt_reply(api_key)  # binance API key
        self.cli_mock_assistant.queue_prompt_reply(
            api_secret)  # binance API secret

        self.async_run_with_timeout(self.app.connect_exchange(exchange))
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=f"\nYou are now connected to {exchange}."))
        self.assertFalse(self.app.placeholder_mode)
        self.assertFalse(self.app.app.hide_input)

    @patch("hummingbot.client.config.security.Security.update_secure_config")
    @patch("hummingbot.client.config.security.Security.encrypted_file_exists")
    @patch("hummingbot.client.config.security.Security.api_keys")
    @patch("hummingbot.user.user_balances.UserBalances.add_exchange")
    def test_connect_exchange_handles_network_timeouts(
        self,
        add_exchange_mock: AsyncMock,
        api_keys_mock: AsyncMock,
        encrypted_file_exists_mock: MagicMock,
        _: MagicMock,
    ):
        add_exchange_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01
        api_key = "someKey"
        api_secret = "someSecret"
        api_keys_mock.return_value = {
            "binance_api_key": api_key,
            "binance_api_secret": api_secret
        }
        encrypted_file_exists_mock.return_value = False
        self.cli_mock_assistant.queue_prompt_reply(api_key)  # binance API key
        self.cli_mock_assistant.queue_prompt_reply(
            api_secret)  # binance API secret

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connect_exchange("binance"))
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection to complete. See logs for more details."
            ))
        self.assertFalse(self.app.placeholder_mode)
        self.assertFalse(self.app.app.hide_input)

    @patch("hummingbot.user.user_balances.UserBalances.update_exchanges")
    def test_connection_df_handles_network_timeouts(
            self, update_exchanges_mock: AsyncMock):
        update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connection_df())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection table to populate. See logs for more details."
            ))

    @patch("hummingbot.user.user_balances.UserBalances.update_exchanges")
    def test_connection_df_handles_network_timeouts_logs_hidden(
            self, update_exchanges_mock: AsyncMock):
        self.cli_mock_assistant.toggle_logs()

        update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connection_df())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection table to populate. See logs for more details."
            ))
예제 #5
0
class ConfigCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.client_config = ClientConfigMap()
        self.config_adapter = ClientConfigAdapter(self.client_config)

        self.app = HummingbotApplication(client_config_map=self.config_adapter)
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        super().tearDown()

    def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1):
        ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout))
        return ret

    @patch("hummingbot.client.hummingbot_application.get_strategy_config_map")
    @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify")
    def test_list_configs(self, notify_mock, get_strategy_config_map_mock):
        captures = []
        notify_mock.side_effect = lambda s: captures.append(s)
        strategy_name = "some-strategy"
        self.app.strategy_name = strategy_name

        strategy_config_map_mock = {
            "five": ConfigVar(key="five", prompt=""),
            "six": ConfigVar(key="six", prompt="", default="sixth"),
        }
        strategy_config_map_mock["five"].value = "fifth"
        strategy_config_map_mock["six"].value = "sixth"
        get_strategy_config_map_mock.return_value = strategy_config_map_mock

        self.app.list_configs()

        self.assertEqual(6, len(captures))
        self.assertEqual("\nGlobal Configurations:", captures[0])

        df_str_expected = ("    +--------------------------+----------------------+\n"
                           "    | Key                      | Value                |\n"
                           "    |--------------------------+----------------------|\n"
                           "    | kill_switch_mode         | kill_switch_disabled |\n"
                           "    | autofill_import          | disabled             |\n"
                           "    | telegram_mode            | telegram_disabled    |\n"
                           "    | send_error_logs          | True                 |\n"
                           "    | pmm_script_mode          | pmm_script_disabled  |\n"
                           "    | gateway                  |                      |\n"
                           "    | ∟ gateway_api_host       | localhost            |\n"
                           "    | ∟ gateway_api_port       | 5000                 |\n"
                           "    | rate_oracle_source       | binance              |\n"
                           "    | global_token             |                      |\n"
                           "    | ∟ global_token_symbol    | $                    |\n"
                           "    | rate_limits_share_pct    | 100                  |\n"
                           "    | commands_timeout         |                      |\n"
                           "    | ∟ create_command_timeout | 10                   |\n"
                           "    | ∟ other_commands_timeout | 30                   |\n"
                           "    | tables_format            | psql                 |\n"
                           "    +--------------------------+----------------------+")

        self.assertEqual(df_str_expected, captures[1])
        self.assertEqual("\nColor Settings:", captures[2])

        df_str_expected = ("    +--------------------+---------+\n"
                           "    | Key                | Value   |\n"
                           "    |--------------------+---------|\n"
                           "    | ∟ top_pane         | #000000 |\n"
                           "    | ∟ bottom_pane      | #000000 |\n"
                           "    | ∟ output_pane      | #262626 |\n"
                           "    | ∟ input_pane       | #1C1C1C |\n"
                           "    | ∟ logs_pane        | #121212 |\n"
                           "    | ∟ terminal_primary | #5FFFD7 |\n"
                           "    +--------------------+---------+")

        self.assertEqual(df_str_expected, captures[3])
        self.assertEqual("\nStrategy Configurations:", captures[4])

        df_str_expected = (
            "    +-------+---------+"
            "\n    | Key   | Value   |"
            "\n    |-------+---------|"
            "\n    | five  | fifth   |"
            "\n    | six   | sixth   |"
            "\n    +-------+---------+"
        )

        self.assertEqual(df_str_expected, captures[5])

    @patch("hummingbot.client.hummingbot_application.get_strategy_config_map")
    @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify")
    def test_list_configs_pydantic_model(self, notify_mock, get_strategy_config_map_mock):
        captures = []
        notify_mock.side_effect = lambda s: captures.append(s)
        strategy_name = "some-strategy"
        self.app.strategy_name = strategy_name

        class DoubleNestedModel(BaseClientModel):
            double_nested_attr: float = Field(default=3.0)

            class Config:
                title = "double_nested_model"

        class NestedModelOne(BaseClientModel):
            nested_attr: str = Field(default="some value")
            double_nested_model: DoubleNestedModel = Field(default=DoubleNestedModel())

            class Config:
                title = "nested_mode_one"

        class NestedModelTwo(BaseClientModel):
            class Config:
                title = "nested_mode_two"

        class DummyModel(BaseClientModel):
            some_attr: int = Field(default=1)
            nested_model: Union[NestedModelTwo, NestedModelOne] = Field(default=NestedModelOne())
            another_attr: Decimal = Field(default=Decimal("1.0"))
            missing_no_default: int = Field(default=...)

            class Config:
                title = "dummy_model"

        get_strategy_config_map_mock.return_value = ClientConfigAdapter(DummyModel.construct())

        self.app.list_configs()

        self.assertEqual(6, len(captures))

        self.assertEqual("\nStrategy Configurations:", captures[4])

        df_str_expected = (
            "    +------------------------+------------------------+"
            "\n    | Key                    | Value                  |"
            "\n    |------------------------+------------------------|"
            "\n    | some_attr              | 1                      |"
            "\n    | nested_model           | nested_mode_one        |"
            "\n    | ∟ nested_attr          | some value             |"
            "\n    | ∟ double_nested_model  |                        |"
            "\n    |   ∟ double_nested_attr | 3.0                    |"
            "\n    | another_attr           | 1.0                    |"
            "\n    | missing_no_default     | &cMISSING_AND_REQUIRED |"
            "\n    +------------------------+------------------------+"
        )

        self.assertEqual(df_str_expected, captures[5])

    @patch("hummingbot.client.hummingbot_application.get_strategy_config_map")
    @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify")
    def test_config_non_configurable_key_fails(self, notify_mock, get_strategy_config_map_mock):
        class DummyModel(BaseStrategyConfigMap):
            strategy: str = Field(default="pure_market_making", client_data=None)
            some_attr: int = Field(default=1, client_data=ClientFieldData(prompt=lambda mi: "some prompt"))
            another_attr: Decimal = Field(default=Decimal("1.0"))

            class Config:
                title = "dummy_model"

        strategy_name = "some-strategy"
        self.app.strategy_name = strategy_name
        get_strategy_config_map_mock.return_value = ClientConfigAdapter(DummyModel.construct())
        self.app.config(key="some_attr")

        notify_mock.assert_not_called()

        self.app.config(key="another_attr")

        notify_mock.assert_called_once_with("Invalid key, please choose from the list.")

        notify_mock.reset_mock()
        self.app.config(key="some_key")

        notify_mock.assert_called_once_with("Invalid key, please choose from the list.")

    @patch("hummingbot.client.command.config_command.save_to_yml")
    @patch("hummingbot.client.hummingbot_application.get_strategy_config_map")
    @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify")
    def test_config_single_keys(self, _, get_strategy_config_map_mock, save_to_yml_mock):
        class NestedModel(BaseClientModel):
            nested_attr: str = Field(
                default="some value", client_data=ClientFieldData(prompt=lambda mi: "some prompt")
            )

            class Config:
                title = "nested_model"

        class DummyModel(BaseStrategyConfigMap):
            strategy: str = Field(default="pure_market_making", client_data=None)
            some_attr: int = Field(default=1, client_data=ClientFieldData(prompt=lambda mi: "some prompt"))
            nested_model: NestedModel = Field(default=NestedModel())

            class Config:
                title = "dummy_model"

        strategy_name = "some-strategy"
        self.app.strategy_name = strategy_name
        self.app.strategy_file_name = f"{strategy_name}.yml"
        config_map = ClientConfigAdapter(DummyModel.construct())
        get_strategy_config_map_mock.return_value = config_map

        self.async_run_with_timeout(self.app._config_single_key(key="some_attr", input_value=2))

        self.assertEqual(2, config_map.some_attr)
        save_to_yml_mock.assert_called_once()

        save_to_yml_mock.reset_mock()
        self.cli_mock_assistant.queue_prompt_reply("3")
        self.async_run_with_timeout(self.app._config_single_key(key="some_attr", input_value=None))

        self.assertEqual(3, config_map.some_attr)
        save_to_yml_mock.assert_called_once()

        save_to_yml_mock.reset_mock()
        self.cli_mock_assistant.queue_prompt_reply("another value")
        self.async_run_with_timeout(self.app._config_single_key(key="nested_model.nested_attr", input_value=None))

        self.assertEqual("another value", config_map.nested_model.nested_attr)
        save_to_yml_mock.assert_called_once()