from __future__ import annotations import asyncio import json import pytest import trading.domain.execution_service as execution_module from trading.domain.approvals import create_approval from trading.domain.execution_contracts import ExecuteApprovedPlanCommand from trading.domain.execution_history import ( begin_execution, finish_execution, reconstruct_execution, record_execution_event, transition_execution, ) from trading.domain.execution_service import ExecutionService from trading.domain.execution_state import FAILED_SAFE from trading.domain.identity_repo import ensure_paper_exchange_account from trading.domain.plan_revalidation import PlanRevalidationResult from trading.domain.plans_repo import create_plan from trading.domain.risk_limits import MarketRiskContext from trading.domain.schema import apply_migrations, connect @pytest.fixture(autouse=True) def _db(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path)) monkeypatch.setenv("TRADING_MODE", "paper") monkeypatch.setenv("HERMES_EXECUTION_ENABLED", "true") ok, messages = apply_migrations() assert ok, messages def _plan() -> dict: return { "symbol": "BTCUSDT", "decision": "LONG", "entry": 100.0, "stop_loss": 95.0, "take_profit": 110.0, "leverage": 3, "quantity": 1.0, "risk_profile": "moderate", "risk_percent": 0.01, "futuresVerified": True, "noTradeGuard": False, "trading_readiness": "ready", "executable": True, "expires_at": "2099-01-01T00:00:00Z", "field_sources": {"ticker": "datasource4", "orderbook": "datasource4"}, "field_metadata": { "ticker": {"freshness": "fresh", "validity": "valid"}, "orderbook": {"freshness": "fresh", "validity": "valid"}, }, } def _accepted(plan_id: str) -> PlanRevalidationResult: return PlanRevalidationResult( revalidation_id=f"rv-{plan_id}", accepted=True, reasons=(), context_hash="revalidation-context-hash", observed_price=100.0, observed_spread_bps=1.25, observed_slippage_percent=0.0002, market_risk=MarketRiskContext( provider_fresh=True, reconciliation_clear=True, spread_bps=1.25, funding_rate=0.0001, liquidity_ok=True, ), orderbook={"bids": [[99.9, 100]], "asks": [[100.0, 100]]}, ) def test_approved_entry_audit_bundle_reconstructs_identifiers_decisions_and_exchange_io(monkeypatch): owner_id = "audit-owner" session_id = "audit-session" account = ensure_paper_exchange_account(owner_id) plan = create_plan( _plan(), owner_id=owner_id, session_id=session_id, account_id=account.account_id ) approval_id = create_approval( plan["plan_id"], plan["plan_hash"], owner_id, session_id=session_id, account_id=account.account_id, ) async def accepted(plan_id, **_kwargs): return _accepted(plan_id) monkeypatch.setattr(execution_module, "revalidate_plan", accepted) result = asyncio.run(ExecutionService().execute_approved_plan( ExecuteApprovedPlanCommand( plan_id=plan["plan_id"], approval_id=approval_id, owner_id=owner_id, session_id=session_id, account_id=account.account_id, idempotency_key="audit-entry-once", correlation_id="incident-correlation-1", ) )) bundle = reconstruct_execution(result["execution_id"]) execution = bundle["execution"] assert execution["execution_id"] == result["execution_id"] assert execution["owner_id"] == owner_id assert execution["session_id"] == session_id assert execution["account_id"] == account.account_id assert execution["plan_id"] == plan["plan_id"] assert execution["approval_id"] == approval_id assert execution["correlation_id"] == "incident-correlation-1" assert execution["payload"]["plan_hash"] == plan["plan_hash"] assert execution["payload"]["provider_snapshot_hash"] == plan["provider_snapshot_hash"] assert execution["payload"]["revalidation_id"] == f"rv-{plan['plan_id']}" assert execution["payload"]["revalidation_context_hash"] == "revalidation-context-hash" assert execution["payload"]["risk_decision"] == "accepted" assert execution["payload"]["risk_context"]["provider_fresh"] is True reasons = [event["reason"] for event in bundle["events"]] assert "exchange_request_prepared" in reasons assert "exchange_result_received" in reasons assert "protection_confirmed" in reasons assert bundle["orders"] order = bundle["orders"][0] assert order["order_id"] == result["order_id"] assert order["events"] assert order["fills"] assert {item["kind"] for item in order["protective_orders"]} == {"stop_loss", "take_profit"} def test_execution_audit_redacts_payloads_events_and_error_text(): secret_values = { "api_key": "API-VERY-SECRET", "password": "PASSWORD-VERY-SECRET", "bearer": "TOKEN-VERY-SECRET", "auth": "AUTH-VERY-SECRET", } execution_id = begin_execution( owner_id="redaction-owner", session_id="redaction-session", account_id=None, symbol="BTC/USDT:USDT", action="entry", mode="paper", correlation_id="redaction-correlation", payload={ "api_key": secret_values["api_key"], "nested": {"password": secret_values["password"]}, "header": f"Bearer {secret_values['bearer']}", }, actor="redaction-owner", ) record_execution_event( execution_id, actor="redaction-owner", reason="redaction_probe", payload={"authorization": f"Bearer {secret_values['auth']}"}, ) transition_execution( execution_id, FAILED_SAFE, actor="redaction-owner", reason="safe_failure", payload={"secret": "TRANSITION-VERY-SECRET"}, ) finish_execution( execution_id, state=FAILED_SAFE, error=RuntimeError( "api_key=ERROR-VERY-SECRET password=ERROR-PASSWORD-VERY-SECRET" ), payload={"refresh_token": "RESULT-VERY-SECRET"}, ) bundle = reconstruct_execution(execution_id) serialized = json.dumps(bundle, sort_keys=True) for value in ( *secret_values.values(), "TRANSITION-VERY-SECRET", "ERROR-VERY-SECRET", "ERROR-PASSWORD-VERY-SECRET", "RESULT-VERY-SECRET", ): assert value not in serialized assert "[REDACTED]" in serialized assert bundle["execution"]["owner_id"] == "redaction-owner" assert bundle["execution"]["session_id"] == "redaction-session" assert bundle["execution"]["correlation_id"] == "redaction-correlation" conn = connect() try: row = conn.execute( "SELECT error_message FROM execution_history WHERE execution_id=?", (execution_id,) ).fetchone() assert row is not None assert "ERROR-VERY-SECRET" not in str(row[0]) assert "[REDACTED]" in str(row[0]) finally: conn.close()