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." ))
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])
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." ))
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()