157 lines
6.5 KiB
Python
157 lines
6.5 KiB
Python
import os
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://user:pass@localhost:5432/ai_ict_test")
|
|
|
|
from src.services.readiness import LaunchReadinessService
|
|
|
|
|
|
class LaunchReadinessServiceTests(unittest.TestCase):
|
|
def test_build_summary_reports_assisted_not_ready_without_real_trade_sample(self) -> None:
|
|
service = LaunchReadinessService(
|
|
market_state_service=_MarketStateService(),
|
|
broker_read_model_service=_BrokerReadService(),
|
|
weekly_review_service=_WeeklyReviewService(),
|
|
trust_service=_TrustService(),
|
|
live_readiness_service=_LiveReadinessService(),
|
|
)
|
|
|
|
with (
|
|
patch("src.services.readiness.launch_readiness_service.get_trading_runtime_service", return_value=_RuntimeService()),
|
|
patch("src.services.readiness.launch_readiness_service.settings.launch_require_real_trade_sample", True),
|
|
):
|
|
payload = service.build_summary(instrument_id=1, timeframe="1m")
|
|
|
|
self.assertTrue(payload["analysis_only_ready"])
|
|
self.assertTrue(payload["assisted_trading_ready"])
|
|
self.assertTrue(payload["operator_soft_rollout_ready"])
|
|
self.assertEqual(payload["operator_soft_rollout_mode"], "execution_assist")
|
|
self.assertTrue(payload["real_trade_sample_gate_required"])
|
|
self.assertEqual(payload["real_trade_sample_gate_status"], "waiting_for_real_fill_sample")
|
|
self.assertIn("wait_for_and_verify_real_fill_sample", payload["next_actions"])
|
|
self.assertFalse(payload["real_trade_loop_ready"])
|
|
self.assertIn("real_trade_sample_verified", payload["warning_reasons"])
|
|
self.assertIn("real_trade_sample_verified", payload["external_waiting_items"])
|
|
self.assertFalse(payload["real_live_ready"])
|
|
|
|
def test_build_summary_defers_real_trade_sample_gate_for_analysis_decision_phase(self) -> None:
|
|
service = LaunchReadinessService(
|
|
market_state_service=_MarketStateService(),
|
|
broker_read_model_service=_BrokerReadService(),
|
|
weekly_review_service=_WeeklyReviewService(),
|
|
trust_service=_TrustService(),
|
|
live_readiness_service=_LiveReadinessService(),
|
|
)
|
|
|
|
with (
|
|
patch("src.services.readiness.launch_readiness_service.get_trading_runtime_service", return_value=_RuntimeService()),
|
|
patch("src.services.readiness.launch_readiness_service.settings.launch_require_real_trade_sample", False),
|
|
):
|
|
payload = service.build_summary(instrument_id=1, timeframe="1m")
|
|
|
|
self.assertTrue(payload["analysis_only_ready"])
|
|
self.assertFalse(payload["real_trade_sample_gate_required"])
|
|
self.assertEqual(payload["real_trade_sample_gate_status"], "deferred_analysis_decision_only")
|
|
self.assertEqual(payload["real_trade_sample_gate_phase"], "deferred_analysis_decision_only")
|
|
self.assertNotIn("real_trade_sample_verified", payload["warning_reasons"])
|
|
self.assertNotIn("real_trade_sample_verified", payload["external_waiting_items"])
|
|
self.assertNotIn("wait_for_and_verify_real_fill_sample", payload["next_actions"])
|
|
|
|
def test_build_summary_exposes_restricted_soft_rollout_when_only_blacklist_models_exist(self) -> None:
|
|
service = LaunchReadinessService(
|
|
market_state_service=_MarketStateService(),
|
|
broker_read_model_service=_BrokerReadService(),
|
|
weekly_review_service=_WeeklyReviewService(),
|
|
trust_service=_BlacklistOnlyTrustService(),
|
|
live_readiness_service=_LiveReadinessService(),
|
|
)
|
|
|
|
with patch("src.services.readiness.launch_readiness_service.get_trading_runtime_service", return_value=_RuntimeService()):
|
|
payload = service.build_summary(instrument_id=1, timeframe="1m")
|
|
|
|
self.assertTrue(payload["analysis_only_ready"])
|
|
self.assertFalse(payload["decision_support_ready"])
|
|
self.assertTrue(payload["restricted_decision_support_ready"])
|
|
self.assertTrue(payload["operator_soft_rollout_ready"])
|
|
self.assertEqual(payload["operator_soft_rollout_mode"], "restricted_decision_support")
|
|
self.assertIn("start_restricted_decision_support_soft_rollout", payload["next_actions"])
|
|
self.assertIn("execution_assist_remains_disabled", payload["operator_soft_rollout_boundary_notes"])
|
|
self.assertNotIn("trust_model_available", payload["warning_reasons"])
|
|
|
|
|
|
class _MarketState:
|
|
freshness = type("Freshness", (), {"code": "market_data_fresh"})()
|
|
stream_state = type("Stream", (), {"code": "market_stream_connected"})()
|
|
data_gap_state = type("Gap", (), {"code": "market_data_gap_clear"})()
|
|
news_window = type("News", (), {"provider_name": "cached", "freshness_code": "news_provider_cached", "error": None})()
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"freshness": {"code": "market_data_fresh"},
|
|
"stream_state": {"code": "market_stream_connected"},
|
|
"data_gap_state": {"code": "market_data_gap_clear"},
|
|
}
|
|
|
|
|
|
class _MarketStateService:
|
|
def build_market_state(self, **kwargs):
|
|
return _MarketState()
|
|
|
|
|
|
class _BrokerState:
|
|
status = "broker_state_ok"
|
|
|
|
def to_dict(self):
|
|
return {"status": "broker_state_ok"}
|
|
|
|
|
|
class _BrokerReadService:
|
|
def fetch_snapshot(self, **kwargs):
|
|
return _BrokerState()
|
|
|
|
|
|
class _WeeklyReviewService:
|
|
def build_summary(self, **kwargs):
|
|
return {"real_trade_count": 0}
|
|
|
|
|
|
class _TrustService:
|
|
def build_summary(self, **kwargs):
|
|
return {
|
|
"whitelist": ["OSOK"],
|
|
"observe_list": [],
|
|
"blacklist": [],
|
|
"analysis_only_list": ["OSOK"],
|
|
"restricted_decision_support_list": ["OSOK"],
|
|
"decision_support_list": ["OSOK"],
|
|
"execution_candidate_list": ["OSOK"],
|
|
}
|
|
|
|
|
|
class _BlacklistOnlyTrustService:
|
|
def build_summary(self, **kwargs):
|
|
return {
|
|
"whitelist": [],
|
|
"observe_list": [],
|
|
"blacklist": ["SWEEP_MSS_FVG"],
|
|
"analysis_only_list": ["SWEEP_MSS_FVG"],
|
|
"restricted_decision_support_list": ["SWEEP_MSS_FVG"],
|
|
"decision_support_list": [],
|
|
"execution_candidate_list": [],
|
|
}
|
|
|
|
|
|
class _LiveReadinessService:
|
|
def build_summary(self, **kwargs):
|
|
return {"ready_for_real_live": False, "blocking_reasons": ["adapter_real_live_not_ready"]}
|
|
|
|
|
|
class _RuntimeService:
|
|
def status(self):
|
|
return {"scheduler_state": "running", "current_blockers": []}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|