"""CLI tests for CoinHunter V2.""" from __future__ import annotations import io import unittest from unittest.mock import patch from coinhunter import cli class CLITestCase(unittest.TestCase): def test_help_includes_v2_commands(self): parser = cli.build_parser() help_text = parser.format_help() self.assertIn("init", help_text) self.assertIn("account", help_text) self.assertIn("buy", help_text) self.assertIn("sell", help_text) self.assertIn("opportunity", help_text) self.assertIn("--doc", help_text) def test_init_dispatches(self): captured = {} with patch.object(cli, "ensure_init_files", return_value={"force": True, "root": "/tmp/ch"}), patch.object( cli, "install_shell_completion", return_value={"shell": "zsh", "installed": True, "path": "/tmp/ch/_coinhunter"} ), patch.object( cli, "print_output", side_effect=lambda payload, **kwargs: captured.setdefault("payload", payload) ): result = cli.main(["init", "--force"]) self.assertEqual(result, 0) self.assertTrue(captured["payload"]["force"]) self.assertIn("completion", captured["payload"]) def test_old_command_is_rejected(self): with self.assertRaises(SystemExit): cli.main(["exec", "bal"]) def test_runtime_error_is_rendered_cleanly(self): stderr = io.StringIO() with patch.object(cli, "load_config", side_effect=RuntimeError("boom")), patch("sys.stderr", stderr): result = cli.main(["market", "tickers", "BTCUSDT"]) self.assertEqual(result, 1) self.assertIn("error: boom", stderr.getvalue()) def test_buy_dispatches(self): captured = {} with patch.object(cli, "load_config", return_value={"binance": {"spot_base_url": "https://test", "recv_window": 5000}, "trading": {"dry_run_default": True}}), patch.object( cli, "get_binance_credentials", return_value={"api_key": "k", "api_secret": "s"} ), patch.object( cli, "SpotBinanceClient" ), patch.object( cli.trade_service, "execute_spot_trade", return_value={"trade": {"status": "DRY_RUN"}} ), patch.object( cli, "print_output", side_effect=lambda payload, **kwargs: captured.setdefault("payload", payload) ): result = cli.main(["buy", "BTCUSDT", "-Q", "100"]) self.assertEqual(result, 0) self.assertEqual(captured["payload"]["trade"]["status"], "DRY_RUN") def test_sell_dispatches(self): captured = {} with patch.object(cli, "load_config", return_value={"binance": {"spot_base_url": "https://test", "recv_window": 5000}, "trading": {"dry_run_default": True}}), patch.object( cli, "get_binance_credentials", return_value={"api_key": "k", "api_secret": "s"} ), patch.object( cli, "SpotBinanceClient" ), patch.object( cli.trade_service, "execute_spot_trade", return_value={"trade": {"status": "DRY_RUN"}} ), patch.object( cli, "print_output", side_effect=lambda payload, **kwargs: captured.setdefault("payload", payload) ): result = cli.main(["sell", "BTCUSDT", "-q", "0.01"]) self.assertEqual(result, 0) self.assertEqual(captured["payload"]["trade"]["status"], "DRY_RUN") def test_doc_flag_prints_documentation(self): import io from unittest.mock import patch stdout = io.StringIO() with patch("sys.stdout", stdout): result = cli.main(["market", "tickers", "--doc"]) self.assertEqual(result, 0) output = stdout.getvalue() self.assertIn("lastPrice", output) self.assertIn("BTCUSDT", output) def test_upgrade_dispatches(self): captured = {} with patch.object(cli, "self_upgrade", return_value={"command": "pipx upgrade coinhunter", "returncode": 0}), patch.object( cli, "print_output", side_effect=lambda payload, **kwargs: captured.setdefault("payload", payload) ): result = cli.main(["upgrade"]) self.assertEqual(result, 0) self.assertEqual(captured["payload"]["returncode"], 0)