test(validation): 토큰 위생 및 플랫폼 통합 검증 체계 고도화
Snapshot Admin Web Validation / validate-snapshot-admin-smoke (push) Has been cancelled
Snapshot Admin Web Validation / validate-snapshot-admin-full (push) Has been cancelled
Quant Engine CI/CD Pipeline / validate-core (pull_request) Has been cancelled
Quant Engine CI/CD Pipeline / validate-ui-and-storage (pull_request) Has been cancelled
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (pull_request) Has been cancelled
Snapshot Admin Web Validation / validate-snapshot-admin-smoke (push) Has been cancelled
Snapshot Admin Web Validation / validate-snapshot-admin-full (push) Has been cancelled
Quant Engine CI/CD Pipeline / validate-core (pull_request) Has been cancelled
Quant Engine CI/CD Pipeline / validate-ui-and-storage (pull_request) Has been cancelled
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (pull_request) Has been cancelled
This commit is contained in:
@@ -12,6 +12,8 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import urllib.request
|
||||
import socket
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
@@ -21,6 +23,8 @@ if str(ROOT) not in sys.path:
|
||||
|
||||
import unittest
|
||||
|
||||
import tools.validate_platform_transition_wbs_v1 as platform_transition_validator
|
||||
import tools.validate_snapshot_admin_web_v1 as snapshot_admin_validator
|
||||
from src.quant_engine import kis_data_collection_v1 as kdc
|
||||
from src.quant_engine.data_collection_store_v1 import load_collection_dashboard_state
|
||||
from src.quant_engine.qualitative_sell_strategy_v1 import compute_qualitative_sell_strategy
|
||||
@@ -175,3 +179,166 @@ class TestKisCollectionIntegration(unittest.TestCase):
|
||||
self.assertEqual(fetched[0]["action"], decision["action"])
|
||||
self.assertEqual(fetched[0]["conviction"], decision["conviction"])
|
||||
self.assertEqual(fetched[0]["market_regime"], decision["market_regime"])
|
||||
|
||||
def test_snapshot_admin_and_platform_transition_validators_remain_passable(self):
|
||||
"""E2E 체인 결과물이 snapshot_admin 및 platform-transition 검증에 그대로 연결되는지 확인."""
|
||||
snapshot_out = ROOT / "Temp" / "snapshot_admin_web_validation_v1.json"
|
||||
transition_out = ROOT / "Temp" / "platform_transition_wbs_v1.json"
|
||||
if snapshot_out.exists():
|
||||
snapshot_out.unlink()
|
||||
if transition_out.exists():
|
||||
transition_out.unlink()
|
||||
|
||||
snapshot_rc = snapshot_admin_validator.main()
|
||||
transition_rc = platform_transition_validator.main()
|
||||
|
||||
snapshot_payload = json.loads(snapshot_out.read_text(encoding="utf-8"))
|
||||
transition_payload = json.loads(transition_out.read_text(encoding="utf-8"))
|
||||
|
||||
self.assertEqual(snapshot_rc, 0)
|
||||
self.assertEqual(snapshot_payload["gate"], "PASS")
|
||||
self.assertGreater(snapshot_payload["settings_rows"], 0)
|
||||
self.assertGreater(snapshot_payload["account_snapshot_rows"], 0)
|
||||
|
||||
self.assertEqual(transition_rc, 0)
|
||||
self.assertEqual(transition_payload["gate"], "PASS")
|
||||
self.assertFalse(transition_payload["missing_criteria"])
|
||||
self.assertFalse(transition_payload["roadmap_missing"])
|
||||
self.assertTrue(transition_payload["checks"]["P1_kis_core_api_collector"]["gate"] == "PASS")
|
||||
self.assertTrue(transition_payload["checks"]["P2_sqlite_canonical_store"]["gate"] == "PASS")
|
||||
self.assertTrue(transition_payload["checks"]["P3_ci_scheduler_cutover"]["gate"] == "PASS")
|
||||
self.assertTrue(transition_payload["checks"]["P4_gas_thin_adapter_minimize"]["gate"] == "PASS")
|
||||
self.assertTrue(transition_payload["checks"]["P5_postgresql_upgrade_path"]["gate"] == "PASS")
|
||||
|
||||
def test_snapshot_admin_collection_run_updates_state_with_live_price(self):
|
||||
"""실제 수집 후 snapshot_admin state가 최신 run 및 live price를 반영하는지 확인."""
|
||||
import tempfile
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmp_path = Path(tmpdir)
|
||||
db_path = tmp_path / "snapshot_admin.db"
|
||||
seed_path = tmp_path / "seed.json"
|
||||
seed_payload = {
|
||||
"data": {
|
||||
"settings": [
|
||||
{"ordinal": 1, "key": "total_asset_krw", "value": 500000000, "note": "seed"},
|
||||
{"ordinal": 2, "key": "settlement_cash_d2_krw", "value": 250000000, "note": "seed"},
|
||||
],
|
||||
"data_feed": [
|
||||
{"Ticker": "005930", "Name": "삼성전자", "Sector": "반도체"},
|
||||
{"Ticker": "000660", "Name": "SK하이닉스", "Sector": "반도체"},
|
||||
],
|
||||
"account_snapshot": [
|
||||
{
|
||||
"captured_at": "2026-06-24T11:15:47+09:00",
|
||||
"account": "real",
|
||||
"account_type": "일반계좌",
|
||||
"ticker": "005930",
|
||||
"name": "삼성전자",
|
||||
"holding_quantity": 10,
|
||||
"available_quantity": 10,
|
||||
"average_cost": 70000,
|
||||
"total_cost": 700000,
|
||||
"current_price": 71000,
|
||||
"market_value": 710000,
|
||||
"profit_loss": 10000,
|
||||
"return_pct": 1.43,
|
||||
"immediate_cash": 0,
|
||||
"settlement_cash_d2": 0,
|
||||
"available_cash": 0,
|
||||
"open_order_amount": 0,
|
||||
"monthly_contribution_limit": 0,
|
||||
"monthly_contribution_used": 0,
|
||||
"parse_status": "CAPTURE_PROVIDED_BUT_NOT_HOLDINGS",
|
||||
"user_confirmed": "N",
|
||||
"stop_price": 65000,
|
||||
"highest_price_since_entry": 72000,
|
||||
"entry_date": "2026-06-01",
|
||||
"entry_stage": "stage_1",
|
||||
"position_type": "core",
|
||||
"last_updated": "2026-06-24T11:15:47+09:00",
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
seed_path.write_text(json.dumps(seed_payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.bind(("127.0.0.1", 0))
|
||||
port = int(sock.getsockname()[1])
|
||||
|
||||
proc = subprocess.Popen(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"src.quant_engine.snapshot_admin_server_v1",
|
||||
"--host",
|
||||
"127.0.0.1",
|
||||
"--port",
|
||||
str(port),
|
||||
"--db",
|
||||
str(db_path),
|
||||
"--seed",
|
||||
str(seed_path),
|
||||
],
|
||||
cwd=ROOT,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
try:
|
||||
base_url = f"http://127.0.0.1:{port}"
|
||||
deadline = time.time() + 20
|
||||
while time.time() < deadline:
|
||||
if proc.poll() is not None:
|
||||
output = proc.stdout.read() if proc.stdout else ""
|
||||
self.fail(f"snapshot_admin server exited early with code {proc.returncode}:\n{output}")
|
||||
try:
|
||||
with urllib.request.urlopen(f"{base_url}/api/state", timeout=2) as response:
|
||||
json.loads(response.read().decode("utf-8"))
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(0.25)
|
||||
else:
|
||||
output = proc.stdout.read() if proc.stdout else ""
|
||||
self.fail(f"snapshot_admin server did not become ready:\n{output}")
|
||||
|
||||
request = urllib.request.Request(
|
||||
f"{base_url}/api/collection/run",
|
||||
data=json.dumps(
|
||||
{
|
||||
"mode": "real",
|
||||
"kis_account": "real",
|
||||
"include_live_kis": True,
|
||||
"allow_naver_fallback": False,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
).encode("utf-8"),
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(request, timeout=120) as response:
|
||||
run_payload = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
with urllib.request.urlopen(f"{base_url}/api/state", timeout=10) as response:
|
||||
state_payload = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
collection = state_payload.get("collection", {})
|
||||
latest_report = collection.get("latest_report", {})
|
||||
latest_run = collection.get("latest_run", {})
|
||||
recent_runs = collection.get("runs", [])
|
||||
|
||||
self.assertEqual(run_payload["status"], "PASS")
|
||||
self.assertTrue(any(run.get("run_id") == run_payload["summary"]["run_id"] for run in recent_runs))
|
||||
self.assertIn(latest_report.get("run_id"), {run_payload["summary"]["run_id"], latest_run.get("run_id")})
|
||||
self.assertGreaterEqual(latest_report["source_counts"]["kis_open_api"], 1)
|
||||
self.assertGreaterEqual(len(latest_report.get("rows", [])), 1)
|
||||
self.assertIsNotNone(latest_report["rows"][0].get("current_price"))
|
||||
finally:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=10)
|
||||
except Exception:
|
||||
proc.kill()
|
||||
|
||||
Reference in New Issue
Block a user