#!/usr/bin/env node const appUrl = process.env.AUTOTRADE_APP_URL || "http://127.0.0.1:3001"; const bypassToken = process.env.AUTOTRADE_DEV_BYPASS_TOKEN || "autotrade-dev-bypass"; async function main() { const compile = await callApi("/api/autotrade/strategies/compile", { method: "POST", body: { prompt: "장 초반 변동성은 보수적으로 보고, ORB와 VWAP 신호가 동시에 나올 때만 진입", selectedTechniques: ["orb", "vwap_reversion"], confidenceThreshold: 0.65, }, }); const validation = await callApi("/api/autotrade/strategies/validate", { method: "POST", body: { cashBalance: 2_000_000, allocationPercent: 10, allocationAmount: 300_000, dailyLossPercent: 2, dailyLossAmount: 30_000, }, }); if (!validation?.validation?.isValid) { throw new Error("리스크 검증이 실패했습니다."); } const start = await callApi("/api/autotrade/sessions/start", { method: "POST", headers: { "x-kis-app-key": "dev-app-key", "x-kis-app-secret": "dev-app-secret", "x-kis-account-no": "12345678-01", "x-kis-trading-env": "mock", }, body: { symbol: "005930", leaderTabId: "autotrade-e2e-tab", effectiveAllocationAmount: validation.validation.effectiveAllocationAmount, effectiveDailyLossLimit: validation.validation.effectiveDailyLossLimit, strategySummary: compile.compiledStrategy.summary, }, }); const heartbeat = await callApi("/api/autotrade/sessions/heartbeat", { method: "POST", body: { sessionId: start.session.sessionId, leaderTabId: "autotrade-e2e-tab", }, }); if (heartbeat.session.sessionId !== start.session.sessionId) { throw new Error("heartbeat 결과의 sessionId가 시작 세션과 다릅니다."); } const active = await callApi("/api/autotrade/sessions/active", { method: "GET", }); if (!active.session || active.session.sessionId !== start.session.sessionId) { throw new Error("active 세션 조회 결과가 기대와 다릅니다."); } const stop = await callApi("/api/autotrade/sessions/stop", { method: "POST", body: { sessionId: start.session.sessionId, reason: "manual", }, }); if (!stop.session || stop.session.runtimeState !== "STOPPED") { throw new Error("세션 중지가 정상 반영되지 않았습니다."); } const activeAfterStop = await callApi("/api/autotrade/sessions/active", { method: "GET", }); if (activeAfterStop.session !== null) { throw new Error("중지 이후 active 세션이 남아 있습니다."); } console.log("[autotrade-session-e2e] PASS: start -> heartbeat -> stop lifecycle verified"); } async function callApi(path, options) { const headers = { "content-type": "application/json", "x-autotrade-dev-bypass": bypassToken, ...(options.headers || {}), }; const response = await fetch(`${appUrl}${path}`, { method: options.method, headers, body: options.body ? JSON.stringify(options.body) : undefined, }); const payload = await response.json().catch(() => null); if (!response.ok) { const message = payload?.message || `${path} failed (${response.status})`; throw new Error(message); } return payload; } main().catch((error) => { const message = error instanceof Error ? error.message : "autotrade lifecycle test failed"; console.error(`[autotrade-session-e2e] FAIL: ${message}`); process.exit(1); });