refactor: address high-priority debt and publish to PyPI
- Fix TOCTOU race conditions by wrapping read-modify-write cycles under single-file locks in execution_state, portfolio_service, precheck_state, state_manager, and precheck_service. - Add missing test coverage (96 tests total): - test_review_service.py (15 tests) - test_check_api.py (6 tests) - test_external_gate.py main branches (+10 tests) - test_trade_execution.py new commands (+8 tests) - Unify all agent-consumed JSON messages to English. - Config-ize hardcoded values (volume filter, schema_version) via get_user_config with sensible defaults. - Add 1-hour TTL to exchange cache with force_new override. - Add ruff and mypy to dev dependencies; fix all type errors. - Add __all__ declarations to 11 service modules. - Sync README with new commands, config tuning docs, and PyPI badge. - Publish package as coinhunter==1.0.0 on PyPI with MIT license. - Deprecate coinhunter-cli==1.0.1 with runtime warning. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
58
tests/test_cli.py
Normal file
58
tests/test_cli.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""Tests for CLI routing and parser behavior."""
|
||||
|
||||
import pytest
|
||||
|
||||
from coinhunter.cli import ALIASES, MODULE_MAP, build_parser, run_python_module
|
||||
|
||||
|
||||
class TestAliases:
|
||||
def test_all_aliases_resolve_to_canonical(self):
|
||||
for alias, canonical in ALIASES.items():
|
||||
assert canonical in MODULE_MAP, f"alias {alias!r} points to missing canonical {canonical!r}"
|
||||
|
||||
def test_no_alias_is_itself_an_alias_loop(self):
|
||||
for alias in ALIASES:
|
||||
assert alias not in ALIASES.values() or alias in MODULE_MAP
|
||||
|
||||
|
||||
class TestModuleMap:
|
||||
def test_all_modules_exist(self):
|
||||
import importlib
|
||||
|
||||
for command, module_name in MODULE_MAP.items():
|
||||
full = f"coinhunter.{module_name}"
|
||||
mod = importlib.import_module(full)
|
||||
assert hasattr(mod, "main"), f"{full} missing main()"
|
||||
|
||||
|
||||
class TestBuildParser:
|
||||
def test_help_includes_commands(self):
|
||||
parser = build_parser()
|
||||
help_text = parser.format_help()
|
||||
assert "coinhunter diag" in help_text
|
||||
assert "coinhunter exec" in help_text
|
||||
|
||||
def test_parses_command_and_args(self):
|
||||
parser = build_parser()
|
||||
ns = parser.parse_args(["exec", "bal"])
|
||||
assert ns.command == "exec"
|
||||
assert "bal" in ns.args
|
||||
|
||||
def test_version_action_exits(self):
|
||||
parser = build_parser()
|
||||
with pytest.raises(SystemExit) as exc:
|
||||
parser.parse_args(["--version"])
|
||||
assert exc.value.code == 0
|
||||
|
||||
|
||||
class TestRunPythonModule:
|
||||
def test_runs_module_main_and_returns_int(self):
|
||||
result = run_python_module("commands.paths", [], "coinhunter paths")
|
||||
assert result == 0
|
||||
|
||||
def test_mutates_sys_argv(self):
|
||||
import sys
|
||||
|
||||
original = sys.argv[:]
|
||||
run_python_module("commands.paths", ["--help"], "coinhunter paths")
|
||||
assert sys.argv == original
|
||||
Reference in New Issue
Block a user