- Add `coin` entry-point alias alongside `coinhunter` - Add short aliases for all commands (e.g., a/acc, m, opp/o, b, s) - Flatten `buy` and `sell` to top-level commands; remove `trade` parent - Add `--doc` flag to print output schema and field descriptions per command - Update README and tests - Bump version to 2.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
96 lines
4.1 KiB
Python
96 lines
4.1 KiB
Python
"""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)
|