feat(core-B): 운영자 sync/verify CLI + cold-start onboarding (BAT-051, REQ-105/106, PR #32)

dcy-034 마지막 조각. sync(슬롯 qty←브로커+warning해제+OQ-A 전이엣지+FR-7 평단seed)/verify(C 재대조·강제해제없음). core-A QC minor 2건 정리 동봉. QC(opus) PASS, 683 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-26 09:53:49 +09:00
committed by GitHub
co-authored by Claude Opus 4.8
parent ced7eda682
commit 07b393d27d
19 changed files with 1948 additions and 39 deletions
+113
View File
@@ -328,3 +328,116 @@ def test_cancel_json_outputs_json(capsys):
assert rc == 0
out = json.loads(capsys.readouterr().out)
assert out["status"] == "ok"
# ---------------------------------------------------------------------------
# sync parser tests (REQ-105 / BAT-051) — parser-level only, no DB/broker
# ---------------------------------------------------------------------------
def test_sync_parser_portfolio_id_accepted():
"""sync --portfolio-id <pid> is parseable."""
parser = cli._build_parser()
args = parser.parse_args(["sync", "--portfolio-id", "PF-1", "--yes"])
assert args.cmd == "sync"
assert args.portfolio_id == "PF-1"
assert args.yes is True
def test_sync_parser_all_accepted():
"""sync --all is parseable."""
parser = cli._build_parser()
args = parser.parse_args(["sync", "--all", "--yes"])
assert args.cmd == "sync"
assert args.all is True
def test_sync_parser_dry_run_accepted():
"""sync --dry-run is parseable."""
parser = cli._build_parser()
args = parser.parse_args(["sync", "--portfolio-id", "PF-1", "--dry-run"])
assert args.dry_run is True
def test_sync_parser_live_accepted():
"""sync --yes --live is parseable."""
parser = cli._build_parser()
args = parser.parse_args(["sync", "--portfolio-id", "PF-1", "--yes", "--live"])
assert args.live is True
def test_sync_no_target_fails(capsys, monkeypatch):
"""sync without --portfolio-id or --all returns rc=1."""
def _fake_sync(args, cfg, confirm):
from trex_core.runtime.cli import _fail
_fail("--portfolio-id <pid> or --all is required", args.json)
return 1
monkeypatch.setattr(cli, "_cmd_sync", _fake_sync)
rc = cli.main(["sync"])
assert rc == 1
def test_sync_requires_yes_when_not_dry_run(capsys, monkeypatch):
"""sync without --yes (and not dry-run) should abort."""
def _fake_sync(args, cfg, confirm):
# Simulate the confirmation prompt returning False (no --yes, interactive aborts)
if not getattr(args, "dry_run", False) and not getattr(args, "yes", False):
if not confirm("Sync?"):
print("aborted")
return 1
return 0
monkeypatch.setattr(cli, "_cmd_sync", _fake_sync)
rc = cli.main(["sync", "--portfolio-id", "PF-1"], confirm=lambda _: False)
assert rc == 1
def test_sync_live_requires_live_flag(capsys, monkeypatch):
"""sync on live account without --live returns rc=2."""
monkeypatch.setattr(cli, "_broker_mode", lambda cfg: ("kis", True))
def _fake_sync(args, cfg, confirm):
broker, is_live = cli._broker_mode(cfg)
if is_live and not getattr(args, "live", False):
cli._fail("live account requires --live (with --yes) to confirm a real sync", args.json)
return 2
return 0
monkeypatch.setattr(cli, "_cmd_sync", _fake_sync)
rc = cli.main(["sync", "--portfolio-id", "PF-1", "--yes"])
assert rc == 2
# ---------------------------------------------------------------------------
# verify parser tests (REQ-106 / BAT-051) — parser-level only
# ---------------------------------------------------------------------------
def test_verify_parser_portfolio_id_accepted():
"""verify --portfolio-id <pid> is parseable."""
parser = cli._build_parser()
args = parser.parse_args(["verify", "--portfolio-id", "PF-1"])
assert args.cmd == "verify"
assert args.portfolio_id == "PF-1"
def test_verify_parser_no_portfolio_id_accepted():
"""verify (no --portfolio-id) is parseable (all C-warned slots)."""
parser = cli._build_parser()
args = parser.parse_args(["verify"])
assert args.cmd == "verify"
assert args.portfolio_id is None
def test_verify_parser_json_accepted():
"""verify --json is parseable."""
parser = cli._build_parser()
args = parser.parse_args(["verify", "--json"])
assert args.json is True
def test_verify_no_db_no_live_flag_needed(capsys, monkeypatch):
"""verify does not require --yes or --live (read-centric, REQ-106)."""
def _fake_verify(args, cfg):
# Simulate: read-centric, no live gate needed
print("no C-warned slots found")
return 0
monkeypatch.setattr(cli, "_cmd_verify", _fake_verify)
rc = cli.main(["verify"])
assert rc == 0