Files
atm/tests/test_main.py
Claude Agent bf70ca3ac7 feat: complete Faza 1 implementation (105 tests green)
All 12 modules built per reviewed plan:
- detector, state_machine (5-state phased FSM), canary, levels Phase B
- notifier fanout (Discord + Telegram, bounded queue, retry, dead-letter)
- audit (JSONL daily rotation), journal, report (weekly R-multiple PnL)
- calibrate + labeler (Tk, lazy-imported), dryrun with acceptance gate
- unified CLI: atm calibrate|label|dryrun|run|journal|report

README + Phase 2 prop-firm TOS audit checklist included.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:17:41 +00:00

138 lines
4.2 KiB
Python

"""Tests for atm.main unified CLI."""
from __future__ import annotations
import os
import subprocess
import sys
import types
from dataclasses import dataclass
from pathlib import Path
from unittest.mock import MagicMock
import pytest
SUBCOMMANDS = ["calibrate", "label", "dryrun", "run", "journal", "report"]
# Ensure subprocess invocations find the atm package even without pip install
_SRC = str(Path(__file__).resolve().parent.parent / "src")
_SUBPROCESS_ENV = {**os.environ, "PYTHONPATH": _SRC}
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _mock_config_class(cfg=None):
"""Return a Config-like class whose load_current() returns *cfg*."""
if cfg is None:
cfg = MagicMock()
mock_cls = MagicMock()
mock_cls.load_current.return_value = cfg
return mock_cls
# ---------------------------------------------------------------------------
# test_help_works
# ---------------------------------------------------------------------------
def test_help_works():
result = subprocess.run(
[sys.executable, "-m", "atm", "--help"],
capture_output=True,
text=True,
env=_SUBPROCESS_ENV,
)
assert result.returncode == 0, result.stderr
# ---------------------------------------------------------------------------
# test_subcommands_listed
# ---------------------------------------------------------------------------
def test_subcommands_listed():
result = subprocess.run(
[sys.executable, "-m", "atm", "--help"],
capture_output=True,
text=True,
env=_SUBPROCESS_ENV,
)
output = result.stdout
for cmd in SUBCOMMANDS:
assert cmd in output, f"Expected subcommand '{cmd}' in --help output"
# ---------------------------------------------------------------------------
# test_dryrun_wiring
# ---------------------------------------------------------------------------
@dataclass
class _DryrunResult:
acceptance_pass: bool
def _make_dryrun_module(acceptance_pass: bool):
mod = types.ModuleType("atm.dryrun")
mod.dryrun = lambda *a, **kw: _DryrunResult(acceptance_pass=acceptance_pass)
mod.print_report = lambda r: None
return mod
def test_dryrun_wiring_pass(monkeypatch, tmp_path):
import atm.main as _main
monkeypatch.setattr("atm.main.dryrun", _make_dryrun_module(acceptance_pass=True))
monkeypatch.setattr("atm.main.Config", _mock_config_class())
with pytest.raises(SystemExit) as exc_info:
_main.main(["dryrun", str(tmp_path)])
assert exc_info.value.code == 0
def test_dryrun_wiring_fail(monkeypatch, tmp_path):
import atm.main as _main
monkeypatch.setattr("atm.main.dryrun", _make_dryrun_module(acceptance_pass=False))
monkeypatch.setattr("atm.main.Config", _mock_config_class())
with pytest.raises(SystemExit) as exc_info:
_main.main(["dryrun", str(tmp_path)])
assert exc_info.value.code == 1
# ---------------------------------------------------------------------------
# test_report_current_week_default
# ---------------------------------------------------------------------------
def test_report_current_week_default(monkeypatch, tmp_path):
import atm.main as _main
# Journal.all returns no entries — report should print a zero-trade week
monkeypatch.setattr("atm.journal.Journal.all", lambda self: [])
# Should not raise; no sys.exit expected
_main.main(["report", "--file", str(tmp_path / "trades.jsonl")])
# ---------------------------------------------------------------------------
# test_run_live_dry
# ---------------------------------------------------------------------------
def test_run_live_dry(monkeypatch):
import atm.main as _main
calls: list[dict] = []
def _mock_run_live(cfg, duration_s=None, capture_stub=False):
calls.append({"cfg": cfg, "duration_s": duration_s, "capture_stub": capture_stub})
monkeypatch.setattr("atm.main.run_live", _mock_run_live)
monkeypatch.setattr("atm.main.Config", _mock_config_class())
_main.main(["run", "--duration", "0"])
assert len(calls) == 1
assert calls[0]["duration_s"] == pytest.approx(0.0)