feat: add catlog command, agent flag reorder, and TUI polish
- Add `coinhunter catlog` with limit/offset pagination for audit logs - Optimize audit log reading with deque to avoid loading all history - Allow `-a/--agent` flag after subcommands - Fix upgrade spinner artifact and empty line issues - Render audit log TUI as timeline with low-saturation event colors - Convert audit timestamps to local timezone in TUI - Remove futures-related capabilities - Add conda environment.yml for development - Bump version to 2.0.9 and update README Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,10 @@ from coinhunter.runtime import get_runtime_paths
|
||||
|
||||
class ConfigRuntimeTestCase(unittest.TestCase):
|
||||
def test_init_files_created_in_coinhunter_home(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir, patch.dict(os.environ, {"COINHUNTER_HOME": str(Path(tmp_dir) / "home")}, clear=False):
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as tmp_dir,
|
||||
patch.dict(os.environ, {"COINHUNTER_HOME": str(Path(tmp_dir) / "home")}, clear=False),
|
||||
):
|
||||
paths = get_runtime_paths()
|
||||
payload = ensure_init_files(paths)
|
||||
self.assertTrue(paths.config_file.exists())
|
||||
@@ -23,10 +26,13 @@ class ConfigRuntimeTestCase(unittest.TestCase):
|
||||
self.assertEqual(payload["root"], str(paths.root))
|
||||
|
||||
def test_load_config_and_env(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir, patch.dict(
|
||||
os.environ,
|
||||
{"COINHUNTER_HOME": str(Path(tmp_dir) / "home")},
|
||||
clear=False,
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as tmp_dir,
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"COINHUNTER_HOME": str(Path(tmp_dir) / "home")},
|
||||
clear=False,
|
||||
),
|
||||
):
|
||||
paths = get_runtime_paths()
|
||||
ensure_init_files(paths)
|
||||
@@ -40,10 +46,13 @@ class ConfigRuntimeTestCase(unittest.TestCase):
|
||||
self.assertEqual(os.environ["BINANCE_API_SECRET"], "def")
|
||||
|
||||
def test_env_file_overrides_existing_environment(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir, patch.dict(
|
||||
os.environ,
|
||||
{"COINHUNTER_HOME": str(Path(tmp_dir) / "home"), "BINANCE_API_KEY": "old_key"},
|
||||
clear=False,
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as tmp_dir,
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"COINHUNTER_HOME": str(Path(tmp_dir) / "home"), "BINANCE_API_KEY": "old_key"},
|
||||
clear=False,
|
||||
),
|
||||
):
|
||||
paths = get_runtime_paths()
|
||||
ensure_init_files(paths)
|
||||
@@ -55,10 +64,13 @@ class ConfigRuntimeTestCase(unittest.TestCase):
|
||||
self.assertEqual(os.environ["BINANCE_API_SECRET"], "new_secret")
|
||||
|
||||
def test_missing_credentials_raise(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir, patch.dict(
|
||||
os.environ,
|
||||
{"COINHUNTER_HOME": str(Path(tmp_dir) / "home")},
|
||||
clear=False,
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as tmp_dir,
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"COINHUNTER_HOME": str(Path(tmp_dir) / "home")},
|
||||
clear=False,
|
||||
),
|
||||
):
|
||||
os.environ.pop("BINANCE_API_KEY", None)
|
||||
os.environ.pop("BINANCE_API_SECRET", None)
|
||||
@@ -68,10 +80,13 @@ class ConfigRuntimeTestCase(unittest.TestCase):
|
||||
get_binance_credentials(paths)
|
||||
|
||||
def test_permission_error_is_explained(self):
|
||||
with tempfile.TemporaryDirectory() as tmp_dir, patch.dict(
|
||||
os.environ,
|
||||
{"COINHUNTER_HOME": str(Path(tmp_dir) / "home")},
|
||||
clear=False,
|
||||
with (
|
||||
tempfile.TemporaryDirectory() as tmp_dir,
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{"COINHUNTER_HOME": str(Path(tmp_dir) / "home")},
|
||||
clear=False,
|
||||
),
|
||||
):
|
||||
paths = get_runtime_paths()
|
||||
with patch("coinhunter.config.ensure_runtime_dirs", side_effect=PermissionError("no write access")):
|
||||
|
||||
Reference in New Issue
Block a user