Files
ai-exchange/tests/unit/test_readonly_live_analysis_smoke_script.py
T
2026-05-21 09:56:43 +08:00

230 lines
12 KiB
Python

import contextlib
import io
import json
import tempfile
import unittest
from pathlib import Path
from scripts import run_readonly_live_analysis_smoke
class ReadonlyLiveAnalysisSmokeScriptTests(unittest.TestCase):
def test_main_writes_passed_json_artifact_without_trade_operations(self) -> None:
output = io.StringIO()
with tempfile.TemporaryDirectory() as tmpdir:
artifact = Path(tmpdir) / "readonly-smoke.json"
opener = _FakeOpener(
{
"/health": {"app": "ai-ict", "database_configured": True},
"/trading/runtime-status": {"scheduler_state": "running", "current_blockers": []},
"/trading/launch-readiness?instrument_id=1&timeframe=1m": {
"live_analysis_assistant_ready": True,
"execution_assist_ready": False,
"real_live_ready": False,
"live_analysis_assistant_summary": {
"ready_for_readonly_live_analysis": True,
"must_not_trade": True,
},
},
"/trading/review-packet?instrument_id=1": {
"status": "blocked",
"action_recommendation": "observe_only",
"decision_assistant_summary": {
"trader_mode_summary": {
"status_label": "Observe Only",
"primary_conclusion": "Observe only.",
"focus_quality_explanation": "No single setup is clean enough.",
"plain_language_reasons": ["No clean focus."],
"recovery_actions": [{"label": "Continue Review"}],
"keyboard_shortcuts": [{"key": "Space", "action": "pause_or_resume_auto_refresh"}],
"profile_template": {"default_timeframe": "1m", "default_queue_instruments": [{"symbol": "BTC"}]},
"multi_instrument_queue_template": {"symbols": ["BTC", "ETH", "SOL"]},
"chart_context_bridge": {"status": "ready"},
"post_market_review_draft": {"status": "draft_ready"},
"ops_duty_panel": {"status": "use_today"},
"freshness_summary": {"status": "fresh"},
"r2_evidence_window": {"status": "collecting_evidence"},
"primary_question_trade_action": "not_a_buy_sell_instruction",
"execution_allowed": False,
"real_live_ready": False,
}
},
},
"/trading/shadow-paper-validation?instrument_id=1&timeframe=1m": {
"status": "collecting_paper_samples",
"execution_boundary": "real_live_still_blocked",
},
"/trading/research/weekly-review?instrument_id=1": {
"entry_count": 0,
"next_week_actions": ["prepare_shadow_paper_weekly_review"],
},
"/trading/maintenance-summary?instrument_id=1&alert_limit=20&visual_limit=20&incident_limit=20": {
"status": "healthy",
"blockers": [],
},
"/trading/unattended-harness?instrument_id=1": {"status": "passed"},
"/candles?instrument_id=1&timeframe=1m&limit=3": {"items": []},
"/trading/backtests?instrument_id=1&limit=1": {"items": [], "latest": None},
"/trading/candidates?instrument_id=1&limit=5": {"items": []},
"/static/app.js": (
"Read-only Live Analysis No order submission Not a buy/sell instruction "
"Execution remains disabled ready_for_readonly_live_analysis must_not_trade"
),
"/trader-mode": (
"Trader Mode Now Wait ICT Fast Read Market Last Updated Review Packet Updated "
"Fast Queue Chart Bridge Duty Panel Profile Sessions Setups Recovery No Trade "
"Need More Evidence After-Close Draft R2 Evidence Window"
),
"/static/trader_mode.js": (
"/trading/review-packet /trading/shadow-paper/observe /trading/operator-notes "
"/trading/research/weekly-review /trading/maintenance-summary /trading/unattended-harness /candles /trading/backtests "
"Do Not Use Review Now Observe Only TRADER_SIGNATURE_KEY TRADER_PROFILE_STORE_KEY ETH London SOL "
"DEFAULT_QUEUE_INSTRUMENTS post_market_review_draft chart_context_bridge ops_duty_panel "
"aria-keyshortcuts pause_or_resume_auto_refresh Historical Edge buildHistoricalEdgeSummary"
),
}
)
original_urlopen = run_readonly_live_analysis_smoke._urlopen_no_proxy
run_readonly_live_analysis_smoke._urlopen_no_proxy = opener
try:
with contextlib.redirect_stdout(output):
exit_code = run_readonly_live_analysis_smoke.main(
argv=[
"--base-url",
"http://example.test",
"--output-json",
"--artifact-path",
str(artifact),
],
env={},
)
finally:
run_readonly_live_analysis_smoke._urlopen_no_proxy = original_urlopen
artifact_payload = json.loads(artifact.read_text(encoding="utf-8"))
payload = json.loads(output.getvalue())
self.assertEqual(exit_code, 0)
self.assertEqual(payload["status"], "passed")
self.assertTrue(payload["ready_for_readonly_live_analysis"])
self.assertTrue(payload["must_not_trade"])
self.assertFalse(payload["submit_cancel_or_live_execution_called"])
self.assertFalse(payload["trade_permission_required"])
self.assertEqual(artifact_payload["status"], "passed")
def test_run_reports_blocker_when_launch_boundary_allows_execution(self) -> None:
payload = run_readonly_live_analysis_smoke.run_readonly_live_analysis_smoke(
base_url="http://example.test",
attempts=1,
sleep_seconds=0,
opener=_FakeOpener(
{
"/health": {"app": "ai-ict"},
"/trading/runtime-status": {"scheduler_state": "running"},
"/trading/launch-readiness?instrument_id=1&timeframe=1m": {
"live_analysis_assistant_ready": True,
"execution_assist_ready": True,
"real_live_ready": False,
"live_analysis_assistant_summary": {
"ready_for_readonly_live_analysis": True,
"must_not_trade": True,
},
},
"/trading/review-packet?instrument_id=1": {
"status": "blocked",
"decision_assistant_summary": {
"trader_mode_summary": {
"status_label": "Do Not Use",
"primary_conclusion": "Paused.",
"focus_quality_explanation": "Paused.",
"plain_language_reasons": ["Runtime is offline."],
"recovery_actions": [{"label": "Restore Runtime"}],
"keyboard_shortcuts": [{"key": "Space", "action": "pause_or_resume_auto_refresh"}],
"profile_template": {"default_timeframe": "1m", "default_queue_instruments": [{"symbol": "BTC"}]},
"multi_instrument_queue_template": {"symbols": ["BTC", "ETH", "SOL"]},
"chart_context_bridge": {"status": "degraded"},
"post_market_review_draft": {"status": "collecting_intraday_actions"},
"ops_duty_panel": {"status": "do_not_use_until_restored"},
"freshness_summary": {"status": "stale"},
"r2_evidence_window": {"status": "collecting_evidence"},
"primary_question_trade_action": "not_a_buy_sell_instruction",
"execution_allowed": False,
"real_live_ready": False,
}
},
},
"/trading/shadow-paper-validation?instrument_id=1&timeframe=1m": {
"status": "collecting_paper_samples",
"execution_boundary": "real_live_still_blocked",
},
"/trading/research/weekly-review?instrument_id=1": {
"entry_count": 0,
"next_week_actions": ["prepare_shadow_paper_weekly_review"],
},
"/trading/maintenance-summary?instrument_id=1&alert_limit=20&visual_limit=20&incident_limit=20": {
"status": "healthy",
"blockers": [],
},
"/trading/unattended-harness?instrument_id=1": {"status": "passed"},
"/candles?instrument_id=1&timeframe=1m&limit=3": {"items": []},
"/trading/backtests?instrument_id=1&limit=1": {"items": [], "latest": None},
"/trading/candidates?instrument_id=1&limit=5": {"items": []},
"/static/app.js": (
"Read-only Live Analysis No order submission Not a buy/sell instruction "
"Execution remains disabled ready_for_readonly_live_analysis must_not_trade"
),
"/trader-mode": (
"Trader Mode Now Wait ICT Fast Read Market Last Updated Review Packet Updated "
"Fast Queue Chart Bridge Duty Panel Profile Sessions Setups Recovery No Trade "
"Need More Evidence After-Close Draft R2 Evidence Window"
),
"/static/trader_mode.js": (
"/trading/review-packet /trading/shadow-paper/observe /trading/operator-notes "
"/trading/research/weekly-review /trading/maintenance-summary /trading/unattended-harness /candles /trading/backtests "
"Do Not Use Review Now Observe Only TRADER_SIGNATURE_KEY TRADER_PROFILE_STORE_KEY ETH London SOL "
"DEFAULT_QUEUE_INSTRUMENTS post_market_review_draft chart_context_bridge ops_duty_panel "
"aria-keyshortcuts pause_or_resume_auto_refresh Historical Edge buildHistoricalEdgeSummary"
),
}
),
)
self.assertEqual(payload["status"], "blocked")
self.assertIn("launch_readiness", payload["blockers"])
class _FakeOpener:
def __init__(self, payloads: dict) -> None:
self.payloads = payloads
def __call__(self, request, timeout_seconds: float):
path = request.full_url.replace("http://example.test", "")
if path not in self.payloads:
raise RuntimeError(f"missing fake response for {path}")
payload = self.payloads[path]
if isinstance(payload, str):
raw = payload.encode("utf-8")
else:
raw = json.dumps(payload).encode("utf-8")
return _FakeResponse(raw)
class _FakeResponse:
status = 200
def __init__(self, raw: bytes) -> None:
self.raw = raw
def __enter__(self):
return self
def __exit__(self, exc_type, exc, traceback):
return False
def read(self):
return self.raw
if __name__ == "__main__":
unittest.main()