import assert from 'node:assert/strict'; import { ApiServer } from './src/services/apiServer.js'; type ChatContext = Parameters[1]; function createServerHarness() { return Object.create(ApiServer.prototype) as any; } function buildContext(): ChatContext { return { profiles: [ { id: 'p1', name: 'High Risk Scalper', allocated_capital: 1000, risk_per_trade_percent: 1.25, symbols: 'BTC/USDT', is_active: true, strategy_config: { rules: [{ ruleId: 'TrendBiasRule', enabled: true }], riskLimits: { maxDailyLossUsd: 50, maxOpenTrades: 2, maxConsecutiveLosses: 2 }, execution: { orderType: 'market', cooldownMinutes: 20, entryMode: 'both' } } } ], runtime: { positions: [ { symbol: 'BTC/USDT', side: 'BUY', entryPrice: 60000, currentPrice: 61500, unrealizedPnl: 375, unrealizedPnlPercent: 2.5, size: 0.25, profileId: 'p1', profileName: 'High Risk Scalper', tradeId: 'trade-1', takeProfit: 63000, stopLoss: 58500, } ], signalContexts: [ { symbol: 'BTC/USDT', profileId: 'p1', profileName: 'High Risk Scalper', signal: 'BUY', passed: false, reason: 'Waiting for stronger rule alignment.', executionStatus: 'SKIPPED', executionCode: 'rule_ratio_not_met', executionReason: 'Only 2 of 4 voting rules passed.', orderId: 'ord-1', } ], recentOrders: [], recentHistory: [ { symbol: 'BTC/USDT', side: 'BUY', pnl: 125, reason: 'Simple target hit', tradeId: 'h1' }, { symbol: 'ETH/USDT', side: 'BUY', pnl: -40, reason: 'Stop loss', tradeId: 'h2' }, ], orderFailures: [ { symbol: 'BTC/USDT', side: 'BUY', qty: 0.1, reason: 'Duplicate entry request blocked', tradeId: 'trade-1', timestamp: Date.now(), } ], operationalEvents: [ { id: 'evt-1', type: 'RECONCILIATION_ALERT', severity: 'WARN', message: 'Reconciliation mismatch detected for BTC/USDT', symbol: 'BTC/USDT', profileId: 'p1', tradeId: 'trade-1', timestamp: Date.now(), } ], accountSnapshot: null, health: { tradingLoopHealthy: true, orderSyncHealthy: true, reconciliationLoopHealthy: false, reconciliationMismatchCount: 2, reconciliationMissingFromExchange: 1, reconciliationMissingInDb: 0, reconciliationNoGoTrades: 1, reconciliationParityQuarantinedTrades: 1, reconciliationParityAutoClosedTrades: 0, reconciliationIntegrityWatchdogTriggered: false, lockContentionCount: 0, reconciliationLockContentionCount: 0, }, settings: { executionMode: 'paper', totalCapital: 1000, riskPerTrade: 1.25, maxOpenTrades: 2, isAlgoEnabled: true, } } }; } function testTradePlanRecommendation() { const server = createServerHarness(); const response = server.buildLocalChatFallback('Recommend a trade plan for my BTC position', buildContext()); assert.equal(response.action, 'recommend_trade_plan'); assert.ok(Array.isArray(response.insights) && response.insights.length > 0, 'trade-plan recommendation must include insights'); assert.ok(response.quickLinks?.some((link: any) => link.kind === 'plans'), 'trade-plan recommendation must include a plans link'); console.log('[PASS] chat fallback builds trade-plan recommendation.'); } function testReconciliationFollowup() { const server = createServerHarness(); const response = server.buildLocalChatFallback('What should I do about reconciliation right now?', buildContext()); assert.equal(response.action, 'recommend_reconciliation_followup'); assert.ok(response.quickLinks?.some((link: any) => link.kind === 'settings'), 'reconciliation follow-up must include settings/admin quick link'); assert.ok(response.insights?.some((entry: string) => entry.includes('Mismatch count')), 'reconciliation follow-up must include mismatch insights'); console.log('[PASS] chat fallback builds reconciliation follow-up.'); } function testRecentTradeReview() { const server = createServerHarness(); const response = server.buildLocalChatFallback('Review my recent trades', buildContext()); assert.equal(response.action, 'review_recent_trades'); assert.ok(response.insights?.some((entry: string) => entry.includes('BTC/USDT')), 'recent trade review must include trade evidence'); console.log('[PASS] chat fallback builds recent-trade review.'); } function testWaitingExplanation() { const server = createServerHarness(); const response = server.buildLocalChatFallback('Why did no trade fire for BTC?', buildContext()); assert.equal(response.action, 'explain_waiting'); assert.ok(response.insights?.some((entry: string) => entry.includes('Execution code')), 'waiting explanation must include execution insight'); console.log('[PASS] chat fallback builds waiting explanation.'); } function main() { testTradePlanRecommendation(); testReconciliationFollowup(); testRecentTradeReview(); testWaitingExplanation(); console.log('Chat copilot fallback checks passed'); } main();