Files
siloqy/prod/clean_arch/violet/test_violet_observe_guard.py

122 lines
3.8 KiB
Python
Raw Normal View History

"""V1: ObserveOnlyVenue — refusal + delegation contract."""
from __future__ import annotations
import asyncio
import sys
sys.path.insert(0, "/mnt/dolphinng5_predict")
sys.path.insert(0, "/mnt/dolphinng5_predict/nautilus_dolphin")
import pytest
from prod.clean_arch.violet.observe_guard import ObserveOnlyVenue, ObserveOnlyViolation
class _Inner:
def __init__(self):
self.connected = False
self.calls = []
def submit(self, intent):
self.calls.append(("submit", intent))
return ["should-never-happen"]
async def submit_async(self, intent):
self.calls.append(("submit_async", intent))
return ["should-never-happen"]
def cancel(self, order, reason=""):
self.calls.append(("cancel", order))
return []
async def cancel_async(self, order, reason=""):
self.calls.append(("cancel_async", order))
return []
async def connect(self):
self.connected = True
async def subscribe(self):
if False: # pragma: no cover — generator shape
yield None
@pytest.mark.asyncio
async def test_submit_and_cancel_raise_and_never_reach_inner():
inner = _Inner()
guard = ObserveOnlyVenue(inner)
with pytest.raises(ObserveOnlyViolation):
guard.submit({"x": 1})
with pytest.raises(ObserveOnlyViolation):
guard.cancel(object())
with pytest.raises(ObserveOnlyViolation):
await guard.submit_async({})
with pytest.raises(ObserveOnlyViolation):
await guard.cancel_async(object())
assert inner.calls == []
@pytest.mark.asyncio
async def test_attribute_get_delegates():
inner = _Inner()
guard = ObserveOnlyVenue(inner)
assert guard.connected is False
await guard.connect()
assert inner.connected is True and guard.connected is True
def test_attribute_set_delegates_to_inner():
"""The runtime does `venue._kernel_ref = kernel` — assignment must land
on the wrapped adapter, not the guard."""
inner = _Inner()
guard = ObserveOnlyVenue(inner)
sentinel = object()
guard._kernel_ref = sentinel
assert getattr(inner, "_kernel_ref") is sentinel
assert not hasattr(type(guard), "_kernel_ref")
def test_wrapped_mock_venue_full_kernel_drive_never_submits():
"""Real ExecutionKernel over a wrapped MOCK venue: an ENTER intent is
refused at the venue boundary; the kernel's venue-submit-failure
rollback converts the refusal into a synthetic REJECT (slot returns to
IDLE) and the inner venue NEVER sees the order."""
from prod.clean_arch.dita_v2.launcher import build_launcher_bundle
from prod.clean_arch.dita_v2.contracts import (
KernelCommandType,
KernelIntent,
TradeSide,
TradeStage,
)
from datetime import datetime, timezone
bundle = build_launcher_bundle(venue_mode="MOCK", max_slots=1)
kernel = bundle.kernel
inner = kernel.venue
submitted = []
orig_submit = inner.submit
inner.submit = lambda *a, **k: submitted.append(a) or orig_submit(*a, **k)
kernel.venue = ObserveOnlyVenue(inner)
intent = KernelIntent(
timestamp=datetime.now(timezone.utc),
intent_id="i-1", trade_id="T-OBS-1", slot_id=0,
asset="BTCUSDT", side=TradeSide.SHORT,
action=KernelCommandType.ENTER,
reference_price=100.0, target_size=1.0, leverage=1.0,
exit_leg_ratios=(1.0,), reason="test", metadata={},
stage=TradeStage.INTENT_CREATED,
)
outcome = kernel.process_intent(intent)
assert submitted == [] # nothing reached the venue
assert kernel.slot(0).is_free() # FSM rolled back to IDLE
assert any(
"VENUE_SUBMIT_ERROR" in str(e.reason)
for e in outcome.emitted_events
), "expected the synthetic REJECT from the guard refusal"
if __name__ == "__main__":
raise SystemExit(pytest.main([__file__, "-v"]))