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:
63
tests/test_runtime.py
Normal file
63
tests/test_runtime.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""Tests for runtime path resolution."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from coinhunter.runtime import RuntimePaths, ensure_runtime_dirs, get_runtime_paths, mask_secret
|
||||
|
||||
|
||||
class TestGetRuntimePaths:
|
||||
def test_defaults_point_to_home_dot_coinhunter(self):
|
||||
paths = get_runtime_paths()
|
||||
assert paths.root == Path.home() / ".coinhunter"
|
||||
|
||||
def test_respects_coinhunter_home_env(self, monkeypatch):
|
||||
monkeypatch.setenv("COINHUNTER_HOME", "~/custom_ch")
|
||||
paths = get_runtime_paths()
|
||||
assert paths.root == Path.home() / "custom_ch"
|
||||
|
||||
def test_respects_hermes_home_env(self, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_HOME", "~/custom_hermes")
|
||||
paths = get_runtime_paths()
|
||||
assert paths.hermes_home == Path.home() / "custom_hermes"
|
||||
assert paths.env_file == Path.home() / "custom_hermes" / ".env"
|
||||
|
||||
def test_returns_frozen_dataclass(self):
|
||||
paths = get_runtime_paths()
|
||||
assert isinstance(paths, RuntimePaths)
|
||||
with pytest.raises(AttributeError):
|
||||
paths.root = Path("/tmp")
|
||||
|
||||
def test_as_dict_returns_strings(self):
|
||||
paths = get_runtime_paths()
|
||||
d = paths.as_dict()
|
||||
assert isinstance(d, dict)
|
||||
assert all(isinstance(v, str) for v in d.values())
|
||||
assert "root" in d
|
||||
|
||||
|
||||
class TestEnsureRuntimeDirs:
|
||||
def test_creates_directories(self, tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("COINHUNTER_HOME", str(tmp_path))
|
||||
paths = get_runtime_paths()
|
||||
returned = ensure_runtime_dirs(paths)
|
||||
assert returned.root.exists()
|
||||
assert returned.state_dir.exists()
|
||||
assert returned.logs_dir.exists()
|
||||
assert returned.cache_dir.exists()
|
||||
assert returned.reviews_dir.exists()
|
||||
|
||||
|
||||
class TestMaskSecret:
|
||||
def test_empty_string(self):
|
||||
assert mask_secret("") == ""
|
||||
|
||||
def test_short_value(self):
|
||||
assert mask_secret("ab") == "**"
|
||||
|
||||
def test_masks_all_but_tail(self):
|
||||
assert mask_secret("supersecret", tail=4) == "*******cret"
|
||||
|
||||
def test_none_returns_empty(self):
|
||||
assert mask_secret(None) == ""
|
||||
Reference in New Issue
Block a user