fix: tighten ledger skip CLI auditing
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
48dfc3e76f
commit
169f222042
@@ -91,7 +91,7 @@ def test_ledger_skips_list_outputs_records(monkeypatch, capsys):
|
|||||||
assert out["records"][0]["skip_reason"] == "missing_execution_id"
|
assert out["records"][0]["skip_reason"] == "missing_execution_id"
|
||||||
|
|
||||||
|
|
||||||
def test_ledger_skips_resolve_marks_record_and_emits_alert(monkeypatch, capsys):
|
def test_ledger_skips_resolve_marks_record_and_emits_alert(monkeypatch, tmp_path, capsys):
|
||||||
class _Store:
|
class _Store:
|
||||||
def __init__(self, *_args, **_kwargs):
|
def __init__(self, *_args, **_kwargs):
|
||||||
pass
|
pass
|
||||||
@@ -125,7 +125,7 @@ def test_ledger_skips_resolve_marks_record_and_emits_alert(monkeypatch, capsys):
|
|||||||
instance="inst-1",
|
instance="inst-1",
|
||||||
yes=True,
|
yes=True,
|
||||||
live=False,
|
live=False,
|
||||||
audit_dir="audit-dir",
|
audit_dir=str(tmp_path),
|
||||||
config=None,
|
config=None,
|
||||||
config_path=None,
|
config_path=None,
|
||||||
env_file=None,
|
env_file=None,
|
||||||
@@ -172,3 +172,74 @@ def test_ledger_skips_resolve_live_requires_live_flag(monkeypatch, tmp_path, cap
|
|||||||
audit = json.loads(next(tmp_path.glob("*.json")).read_text(encoding="utf-8"))
|
audit = json.loads(next(tmp_path.glob("*.json")).read_text(encoding="utf-8"))
|
||||||
assert audit["request"]["instance"] == "inst-1"
|
assert audit["request"]["instance"] == "inst-1"
|
||||||
assert audit["result"]["status"] == "rejected"
|
assert audit["result"]["status"] == "rejected"
|
||||||
|
assert audit["audit_path"].endswith(".json")
|
||||||
|
|
||||||
|
|
||||||
|
def test_ledger_skips_resolve_live_requires_yes_and_live(monkeypatch, tmp_path, capsys):
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"trex_core.adapters.persistence.ledger_skip_store.PostgreSQLLedgerSkipStore",
|
||||||
|
lambda *_args, **_kwargs: object(),
|
||||||
|
)
|
||||||
|
args = SimpleNamespace(
|
||||||
|
cmd="ledger",
|
||||||
|
ledger_cmd="skips",
|
||||||
|
ledger_skips_cmd="resolve",
|
||||||
|
skip_key="abc",
|
||||||
|
reason="operator_reconciled",
|
||||||
|
instance="inst-1",
|
||||||
|
yes=False,
|
||||||
|
live=True,
|
||||||
|
audit_dir=str(tmp_path),
|
||||||
|
config=None,
|
||||||
|
config_path=None,
|
||||||
|
env_file=None,
|
||||||
|
json=True,
|
||||||
|
timeout=10.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
rc = cli._cmd_ledger_skips_resolve(args, _cfg(live=True), lambda _: True)
|
||||||
|
|
||||||
|
assert rc == 2
|
||||||
|
err = capsys.readouterr().err
|
||||||
|
assert "--yes --live" in err
|
||||||
|
audit = json.loads(next(tmp_path.glob("*.json")).read_text(encoding="utf-8"))
|
||||||
|
assert audit["result"]["reason"] == "live account requires --yes --live"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ledger_skips_resolve_not_found_emits_audit_report(monkeypatch, tmp_path, capsys):
|
||||||
|
class _Store:
|
||||||
|
def __init__(self, *_args, **_kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def resolve_by_key(self, *_args, **_kwargs):
|
||||||
|
return None
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"trex_core.adapters.persistence.ledger_skip_store.PostgreSQLLedgerSkipStore",
|
||||||
|
_Store,
|
||||||
|
)
|
||||||
|
args = SimpleNamespace(
|
||||||
|
cmd="ledger",
|
||||||
|
ledger_cmd="skips",
|
||||||
|
ledger_skips_cmd="resolve",
|
||||||
|
skip_key="missing",
|
||||||
|
reason="operator_reconciled",
|
||||||
|
instance="inst-1",
|
||||||
|
yes=True,
|
||||||
|
live=False,
|
||||||
|
audit_dir=str(tmp_path),
|
||||||
|
config=None,
|
||||||
|
config_path=None,
|
||||||
|
env_file=None,
|
||||||
|
json=False,
|
||||||
|
timeout=10.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
rc = cli._cmd_ledger_skips_resolve(args, _cfg(), lambda _: True)
|
||||||
|
|
||||||
|
assert rc == 1
|
||||||
|
out = json.loads(capsys.readouterr().out)
|
||||||
|
assert out["result"]["status"] == "not_found"
|
||||||
|
assert out["audit_path"].endswith(".json")
|
||||||
|
audit = json.loads(next(tmp_path.glob("*.json")).read_text(encoding="utf-8"))
|
||||||
|
assert audit["audit_path"].endswith(".json")
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ class PostgreSQLLedgerSkipStore:
|
|||||||
now = datetime.now(tz=UTC)
|
now = datetime.now(tz=UTC)
|
||||||
notify = False
|
notify = False
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
with self._psycopg.connect(self._dsn, autocommit=False) as conn, conn.cursor() as cur:
|
try:
|
||||||
try:
|
with self._psycopg.connect(self._dsn, autocommit=False) as conn, conn.cursor() as cur:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT INTO ledger_skip "
|
"INSERT INTO ledger_skip "
|
||||||
"(skip_key, instance_key, slot_id, portfolio_id, symbol, order_id, "
|
"(skip_key, instance_key, slot_id, portfolio_id, symbol, order_id, "
|
||||||
@@ -72,8 +72,8 @@ class PostgreSQLLedgerSkipStore:
|
|||||||
conn.commit()
|
conn.commit()
|
||||||
notify = True
|
notify = True
|
||||||
break
|
break
|
||||||
except self._psycopg.errors.UniqueViolation:
|
except self._psycopg.errors.UniqueViolation:
|
||||||
conn.rollback()
|
with self._psycopg.connect(self._dsn, autocommit=False) as conn, conn.cursor() as cur:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"SELECT status FROM ledger_skip WHERE skip_key = %s FOR UPDATE",
|
"SELECT status FROM ledger_skip WHERE skip_key = %s FOR UPDATE",
|
||||||
(skip_key,),
|
(skip_key,),
|
||||||
|
|||||||
@@ -401,6 +401,7 @@ def _ledger_backfill_audit_path(args, execution_id: str) -> Path:
|
|||||||
|
|
||||||
def _write_ledger_backfill_audit(args, report: dict) -> str:
|
def _write_ledger_backfill_audit(args, report: dict) -> str:
|
||||||
path = _ledger_backfill_audit_path(args, report["request"]["execution_id"])
|
path = _ledger_backfill_audit_path(args, report["request"]["execution_id"])
|
||||||
|
report["audit_path"] = str(path)
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
path.write_text(_json.dumps(report, ensure_ascii=False, indent=2, default=str) + "\n",
|
path.write_text(_json.dumps(report, ensure_ascii=False, indent=2, default=str) + "\n",
|
||||||
encoding="utf-8")
|
encoding="utf-8")
|
||||||
@@ -419,6 +420,7 @@ def _ledger_skip_resolve_audit_path(args, skip_key: str) -> Path:
|
|||||||
|
|
||||||
def _write_ledger_skip_resolve_audit(args, report: dict) -> str:
|
def _write_ledger_skip_resolve_audit(args, report: dict) -> str:
|
||||||
path = _ledger_skip_resolve_audit_path(args, report["request"]["skip_key"])
|
path = _ledger_skip_resolve_audit_path(args, report["request"]["skip_key"])
|
||||||
|
report["audit_path"] = str(path)
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
path.write_text(_json.dumps(report, ensure_ascii=False, indent=2, default=str) + "\n",
|
path.write_text(_json.dumps(report, ensure_ascii=False, indent=2, default=str) + "\n",
|
||||||
encoding="utf-8")
|
encoding="utf-8")
|
||||||
@@ -1101,10 +1103,10 @@ def _cmd_ledger_skips_resolve(args, cfg, confirm) -> int:
|
|||||||
"record": None,
|
"record": None,
|
||||||
"audit_path": None,
|
"audit_path": None,
|
||||||
}
|
}
|
||||||
if is_live and not getattr(args, "live", False):
|
if is_live and (not getattr(args, "yes", False) or not getattr(args, "live", False)):
|
||||||
report["result"] = {"status": "rejected", "reason": "live account requires --live (with --yes)"}
|
report["result"] = {"status": "rejected", "reason": "live account requires --yes --live"}
|
||||||
report["audit_path"] = _write_ledger_skip_resolve_audit(args, report)
|
report["audit_path"] = _write_ledger_skip_resolve_audit(args, report)
|
||||||
_fail("live account requires --live (with --yes)", args.json)
|
_fail("live account requires --yes --live", args.json)
|
||||||
if args.json:
|
if args.json:
|
||||||
_emit(report, as_json=True)
|
_emit(report, as_json=True)
|
||||||
else:
|
else:
|
||||||
@@ -1139,6 +1141,8 @@ def _cmd_ledger_skips_resolve(args, cfg, confirm) -> int:
|
|||||||
_fail(f"unresolved ledger skip not found: {args.skip_key}", args.json)
|
_fail(f"unresolved ledger skip not found: {args.skip_key}", args.json)
|
||||||
if args.json:
|
if args.json:
|
||||||
_emit(report, as_json=True)
|
_emit(report, as_json=True)
|
||||||
|
else:
|
||||||
|
_emit_pretty_json(report)
|
||||||
return 1
|
return 1
|
||||||
emitter, transport = _default_alarm_emitter(cfg)
|
emitter, transport = _default_alarm_emitter(cfg)
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user