learning_ai_invt_trdg/web/src/hooks/useWebSocket.test.ts

146 lines
4.3 KiB
TypeScript

import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it, vi } from 'vitest';
import {
DEFAULT_BOT_STATE,
appendAlertUpdate,
appendHistoryUpdate,
applySymbolUpdate,
replaceOrdersUpdate,
replacePositionsUpdate,
replaceSettingsUpdate,
useWebSocket
} from './useWebSocket';
vi.mock('../lib/supabaseClient', () => ({
supabase: {
auth: {
getSession: vi.fn(async () => ({ data: { session: null } }))
}
}
}));
vi.mock('socket.io-client', () => ({
io: vi.fn(() => ({
on: vi.fn(),
close: vi.fn()
}))
}));
describe('useWebSocket', () => {
it('provides default bot state before any socket updates', () => {
const Probe = () => {
const { botState, connected } = useWebSocket('http://localhost:5000');
return React.createElement(
'div',
null,
`${botState.settings.executionMode}|${botState.settings.maxOpenTrades}|${connected ? '1' : '0'}|${Object.keys(botState.symbols).length}`
);
};
const html = renderToStaticMarkup(React.createElement(Probe));
expect(html).toContain('Alerts|3|0|0');
});
it('applies symbol updates without mutating previous symbols', () => {
const prev = {
...DEFAULT_BOT_STATE,
symbols: {
'BTC/USD': {
price: 100,
change24h: 1,
changeToday: 0.5,
session: 'NY',
volatility: 'High',
signal: 'BUY',
priceHistory: [],
rules: {},
indicators: {}
}
}
} as any;
const next = applySymbolUpdate(prev, 'ETH/USD', {
price: 200,
change24h: -0.5,
changeToday: 0.1,
session: 'LDN',
volatility: 'Med',
signal: 'NONE',
priceHistory: [],
rules: {},
indicators: {}
} as any);
expect(Object.keys(prev.symbols)).toEqual(['BTC/USD']);
expect(Object.keys(next.symbols)).toEqual(['BTC/USD', 'ETH/USD']);
expect(next.symbols['ETH/USD'].price).toBe(200);
});
it('appends alert and history updates in order', () => {
const withAlert = appendAlertUpdate(DEFAULT_BOT_STATE, {
timestamp: 1,
type: 'info',
symbol: 'BTC/USD',
message: 'alert-1'
});
const withHistory = appendHistoryUpdate(withAlert, {
symbol: 'BTC/USD',
side: 'BUY',
entryPrice: 100,
exitPrice: 110,
size: 1,
pnl: 10,
pnlPercent: 10,
reason: 'tp',
timestamp: 2
});
expect(withAlert.alerts).toHaveLength(1);
expect(withAlert.alerts[0].message).toBe('alert-1');
expect(withHistory.history).toHaveLength(1);
expect(withHistory.history[0].pnl).toBe(10);
});
it('replaces positions, orders and settings atomically', () => {
const withPositions = replacePositionsUpdate(DEFAULT_BOT_STATE, [{
id: 'p1',
symbol: 'BTC/USD',
side: 'BUY',
size: 1,
entryPrice: 100,
currentPrice: 101,
stopLoss: 95,
takeProfit: 110,
unrealizedPnl: 1,
unrealizedPnlPercent: 1,
marketValue: 101
}]);
const withOrders = replaceOrdersUpdate(withPositions, [{
id: 'o1',
symbol: 'BTC/USD',
type: 'market',
side: 'buy',
qty: 1,
price: 100,
status: 'filled',
timestamp: 1
}]);
const withSettings = replaceSettingsUpdate(withOrders, {
executionMode: 'Paper',
riskPerTrade: 0.02,
totalCapital: 5000,
maxOpenTrades: 5,
isAlgoEnabled: true,
enabledRules: ['TrendBiasRule']
});
expect(withSettings.positions).toHaveLength(1);
expect(withSettings.orders).toHaveLength(1);
expect(withSettings.settings.executionMode).toBe('Paper');
expect(withSettings.settings.isAlgoEnabled).toBe(true);
});
});