test: cover unmanaged position lifecycle flow
Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
co-authored by
genichin
parent
fe51d211d9
commit
f2b825e720
@@ -32,7 +32,7 @@ class FakeBroker:
|
||||
|
||||
class RaisingBroker(FakeBroker):
|
||||
def get_balance(self):
|
||||
raise BrokerBalanceError("broker_unavailable")
|
||||
raise BrokerBalanceError("transport_failed")
|
||||
|
||||
|
||||
def _wire():
|
||||
|
||||
+2
-3
@@ -96,7 +96,6 @@ def pg_clean(pg_dsn: str):
|
||||
from trex_core.adapters.persistence.migrate import apply_migrations
|
||||
|
||||
apply_migrations(pg_dsn)
|
||||
with psycopg.connect(pg_dsn, autocommit=True) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f"TRUNCATE {', '.join(_PG_TABLES)}")
|
||||
with psycopg.connect(pg_dsn, autocommit=True) as conn, conn.cursor() as cur:
|
||||
cur.execute(f"TRUNCATE {', '.join(_PG_TABLES)}")
|
||||
return pg_dsn
|
||||
|
||||
@@ -383,10 +383,13 @@ 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
|
||||
if (
|
||||
not getattr(args, "dry_run", False)
|
||||
and not getattr(args, "yes", False)
|
||||
and 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)
|
||||
@@ -398,7 +401,7 @@ def test_sync_live_requires_live_flag(capsys, monkeypatch):
|
||||
monkeypatch.setattr(cli, "_broker_mode", lambda cfg: ("kis", True))
|
||||
|
||||
def _fake_sync(args, cfg, confirm):
|
||||
broker, is_live = cli._broker_mode(cfg)
|
||||
_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
|
||||
@@ -415,7 +418,7 @@ def test_sync_fail_closed_skipped_error_exits_nonzero_without_writes(
|
||||
"""REQ-123: a fail-closed skipped live sync is a CLI failure."""
|
||||
class _Provider:
|
||||
def get_broker_for_read(self):
|
||||
return SimpleNamespace(get_balance=lambda: []), None
|
||||
return SimpleNamespace(get_balance=list), None
|
||||
|
||||
class _WarningStore:
|
||||
def clear_warned(self, *_args):
|
||||
@@ -452,7 +455,7 @@ def test_sync_fail_closed_skipped_error_exits_nonzero_without_writes(
|
||||
}
|
||||
|
||||
psycopg = types.ModuleType("psycopg")
|
||||
setattr(psycopg, "connect", lambda *_args, **_kwargs: _Connection())
|
||||
psycopg.connect = lambda *_args, **_kwargs: _Connection()
|
||||
monkeypatch.setitem(sys.modules, "psycopg", psycopg)
|
||||
monkeypatch.setattr("trex_core.runtime.broker_provider.build_broker_provider", lambda _cfg: _Provider())
|
||||
monkeypatch.setattr("trex_core.adapters.portfolio.reconcile_warning_store.PostgreSQLReconcileWarningStore", lambda *_args: _WarningStore())
|
||||
@@ -707,10 +710,10 @@ def test_verify_cmd_broker_import_does_not_raise(monkeypatch):
|
||||
|
||||
# Patch at module level — _cmd_verify uses local `from ... import` so we
|
||||
# must patch the source module, not the cli namespace.
|
||||
import trex_core.adapters.persistence.postgresql as pg_mod
|
||||
import trex_core.adapters.portfolio.reconcile_warning_store as rws_mod
|
||||
from trex_core.application.portfolio import InMemoryReconcileWarningStore
|
||||
from trex_core.domain.slot.in_memory_repository import InMemorySlotRepository
|
||||
import trex_core.adapters.portfolio.reconcile_warning_store as rws_mod
|
||||
import trex_core.adapters.persistence.postgresql as pg_mod
|
||||
|
||||
monkeypatch.setattr(rws_mod, "PostgreSQLReconcileWarningStore",
|
||||
lambda dsn: InMemoryReconcileWarningStore())
|
||||
|
||||
@@ -12,8 +12,8 @@ from __future__ import annotations
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
from typing import Callable, Optional
|
||||
from collections.abc import Callable
|
||||
from datetime import UTC, date, datetime, time, timedelta, timezone
|
||||
|
||||
from trex_core.application.alarm_emitter import AlarmEmitter
|
||||
from trex_core.application.broker_port import BrokerPort
|
||||
@@ -23,10 +23,10 @@ from trex_core.application.portfolio import (
|
||||
PortfolioHubReadPort,
|
||||
PortfolioHubUnavailableError,
|
||||
ReconcileNotificationStore,
|
||||
UnmanagedPositionStore,
|
||||
ReconcileWarningStore,
|
||||
SnapshotStore,
|
||||
TradingContext,
|
||||
UnmanagedPositionStore,
|
||||
)
|
||||
from trex_core.application.reconciliation import reconcile_positions
|
||||
|
||||
@@ -45,32 +45,32 @@ class SnapshotScheduler:
|
||||
store: SnapshotStore,
|
||||
broker: BrokerPort,
|
||||
schedule_kst: time = time(hour=7, minute=30),
|
||||
clock: Optional[Callable[[], datetime]] = None,
|
||||
clock: Callable[[], datetime] | None = None,
|
||||
slot_repo=None,
|
||||
slot_service=None,
|
||||
instance_key: str = "default",
|
||||
alarm_emitter: Optional[AlarmEmitter] = None,
|
||||
warn_store: Optional[ReconcileWarningStore] = None,
|
||||
notification_store: Optional[ReconcileNotificationStore] = None,
|
||||
unmanaged_position_store: Optional[UnmanagedPositionStore] = None,
|
||||
alarm_emitter: AlarmEmitter | None = None,
|
||||
warn_store: ReconcileWarningStore | None = None,
|
||||
notification_store: ReconcileNotificationStore | None = None,
|
||||
unmanaged_position_store: UnmanagedPositionStore | None = None,
|
||||
credential_fetch_enabled: bool = False,
|
||||
kis_reinit_callback: Optional[Callable[[BrokerCredentialPayload], None]] = None,
|
||||
credential_refresh: Optional[Callable[[], object]] = None,
|
||||
kis_reinit_callback: Callable[[BrokerCredentialPayload], None] | None = None,
|
||||
credential_refresh: Callable[[], object] | None = None,
|
||||
) -> None:
|
||||
self._read = read_port
|
||||
self._store = store
|
||||
self._broker = broker
|
||||
self._schedule_kst = schedule_kst
|
||||
self._clock = clock or (lambda: datetime.now(tz=timezone.utc))
|
||||
self._daemon_id: Optional[str] = None
|
||||
self._last_fired_date: Optional[datetime] = None
|
||||
self._clock = clock or (lambda: datetime.now(tz=UTC))
|
||||
self._daemon_id: str | None = None
|
||||
self._last_fired_date: datetime | None = None
|
||||
# dcy-020 / REQ-058 (BAT-030): asset-centric slot reconcile after each
|
||||
# universe fetch — always on when slot_repo is wired (the reconcile is
|
||||
# what creates/retires the permanent asset slots).
|
||||
self._slot_repo = slot_repo
|
||||
self._slot_service = slot_service
|
||||
self._instance_key = instance_key
|
||||
self._last_universe_size: Optional[int] = None
|
||||
self._last_universe_size: int | None = None
|
||||
# dcy-033 / REQ-100: best-effort alarm emitter.
|
||||
self._alarm_emitter = alarm_emitter
|
||||
# BAT-050 / REQ-103: durable reconcile-warning store (Optional).
|
||||
@@ -84,7 +84,9 @@ class SnapshotScheduler:
|
||||
# dcy-036 / REQ-113 AC-2 / REQ-115 AC-1: optional callback to reinitialize
|
||||
# KISBroker with the fetched day-start credential. None = no reinit (paper
|
||||
# broker or credential_fetch_enabled=False). Set by daemon.py assembly.
|
||||
self._kis_reinit_callback: Optional[Callable[[BrokerCredentialPayload], None]] = kis_reinit_callback
|
||||
self._kis_reinit_callback: Callable[[BrokerCredentialPayload], None] | None = (
|
||||
kis_reinit_callback
|
||||
)
|
||||
# REQ-119: provider-owned refresh supersedes the legacy raw fetch hook.
|
||||
self._credential_refresh = credential_refresh
|
||||
# REQ-113: detect break-glass at init time (env var set by operator).
|
||||
@@ -93,8 +95,8 @@ class SnapshotScheduler:
|
||||
in ("1", "true", "yes")
|
||||
)
|
||||
# REQ-115: day-scoped credential cache. None until first successful fetch.
|
||||
self._cached_credential: Optional[BrokerCredentialPayload] = None
|
||||
self._credential_date: Optional[date] = None
|
||||
self._cached_credential: BrokerCredentialPayload | None = None
|
||||
self._credential_date: date | None = None
|
||||
# REQ-114: entry gate. True = new entries allowed (default).
|
||||
# credential_fetch_enabled=False keeps this always True (no gate).
|
||||
self._entry_allowed: bool = True
|
||||
@@ -266,7 +268,7 @@ class SnapshotScheduler:
|
||||
try:
|
||||
self._credential_refresh()
|
||||
self._entry_allowed = True
|
||||
except Exception as exc: # provider exposes only sanitized failure
|
||||
except Exception as exc: # noqa: BLE001 - provider exposes only sanitized failure
|
||||
_log.error("DAY_START_LOAD_FAILED: broker credential refresh failed: %s (instance=%s)",
|
||||
exc.__class__.__name__, self._instance_key)
|
||||
self._entry_allowed = False
|
||||
|
||||
@@ -737,12 +737,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
def _cmd_sync(args, cfg, confirm) -> int:
|
||||
"""REQ-105 (dcy-034, BAT-051): sync slot qty to broker ground truth."""
|
||||
|
||||
from trex_core.adapters.portfolio.reconcile_warning_store import (
|
||||
PostgreSQLReconcileWarningStore,
|
||||
)
|
||||
from trex_core.adapters.portfolio.reconcile_notification_store import (
|
||||
PostgreSQLReconcileNotificationStore,
|
||||
)
|
||||
from trex_core.adapters.portfolio.reconcile_warning_store import (
|
||||
PostgreSQLReconcileWarningStore,
|
||||
)
|
||||
from trex_core.application.reconciliation import sync_slot_to_broker
|
||||
|
||||
if not args.portfolio_id and not args.all:
|
||||
@@ -891,12 +891,12 @@ def _cmd_sync(args, cfg, confirm) -> int:
|
||||
def _cmd_verify(args, cfg) -> int:
|
||||
"""REQ-106 (dcy-034, BAT-051): re-check C-case reconcile-warning (read-centric)."""
|
||||
from trex_core.adapters.persistence.postgresql import PostgreSQLSlotRepository
|
||||
from trex_core.adapters.portfolio.reconcile_warning_store import (
|
||||
PostgreSQLReconcileWarningStore,
|
||||
)
|
||||
from trex_core.adapters.portfolio.reconcile_notification_store import (
|
||||
PostgreSQLReconcileNotificationStore,
|
||||
)
|
||||
from trex_core.adapters.portfolio.reconcile_warning_store import (
|
||||
PostgreSQLReconcileWarningStore,
|
||||
)
|
||||
from trex_core.application.portfolio import SnapshotStore
|
||||
from trex_core.application.reconciliation import verify_slot_reconcile
|
||||
|
||||
|
||||
@@ -31,11 +31,11 @@ from trex_core.adapters.messaging import (
|
||||
RabbitMQTransport,
|
||||
topology,
|
||||
)
|
||||
from trex_core.adapters.persistence.migrate import apply_migrations
|
||||
from trex_core.adapters.persistence.account_snapshot import (
|
||||
PostgreSQLAccountSnapshotStore,
|
||||
)
|
||||
from trex_core.adapters.persistence.ledger_skip_store import PostgreSQLLedgerSkipStore
|
||||
from trex_core.adapters.persistence.migrate import apply_migrations
|
||||
from trex_core.adapters.persistence.postgresql import (
|
||||
PgStrategyStateStore,
|
||||
PostgreSQLIntentStore,
|
||||
@@ -55,16 +55,15 @@ from trex_core.adapters.portfolio.reconcile_notification_store import (
|
||||
from trex_core.adapters.portfolio.unmanaged_position_store import (
|
||||
PostgreSQLUnmanagedPositionStore,
|
||||
)
|
||||
from trex_core.application.alarm_emitter import AlarmEmitter
|
||||
from trex_core.application.account_snapshot import AccountSnapshotService
|
||||
from trex_core.application.alarm_emitter import AlarmEmitter
|
||||
from trex_core.application.broker_port import BrokerPort
|
||||
from trex_core.application.execution import ExecutionService
|
||||
from trex_core.application.intents import IntentService
|
||||
from trex_core.application.intents import BuyIntent
|
||||
from trex_core.application.intents import BuyIntent, IntentService
|
||||
from trex_core.application.killswitch import KillSwitchService
|
||||
from trex_core.application.ledger_outbox import LedgerWriter
|
||||
from trex_core.application.ledger_port import LedgerPort
|
||||
from trex_core.application.messaging import MessagingTransport
|
||||
from trex_core.application.killswitch import KillSwitchService
|
||||
from trex_core.application.messaging_handlers import (
|
||||
make_control_rpc_handler,
|
||||
make_intent_rpc_handler,
|
||||
|
||||
Reference in New Issue
Block a user