168 lines
5.1 KiB
TypeScript
168 lines
5.1 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { act, renderHook, waitFor } from '@testing-library/react';
|
|
import { useWebSocket } from './useWebSocket';
|
|
|
|
const { getPlatformAccessTokenMock, ioMock } = vi.hoisted(() => ({
|
|
getPlatformAccessTokenMock: vi.fn(),
|
|
ioMock: vi.fn()
|
|
}));
|
|
|
|
vi.mock('../lib/authSession', () => ({
|
|
getPlatformAccessToken: getPlatformAccessTokenMock
|
|
}));
|
|
|
|
vi.mock('socket.io-client', () => ({
|
|
io: ioMock
|
|
}));
|
|
|
|
describe('useWebSocket DOM/event behavior', () => {
|
|
const handlers: Record<string, (...args: any[]) => void> = {};
|
|
let socketStub: any;
|
|
|
|
beforeEach(() => {
|
|
Object.keys(handlers).forEach((key) => delete handlers[key]);
|
|
getPlatformAccessTokenMock.mockReset();
|
|
ioMock.mockReset();
|
|
|
|
socketStub = {
|
|
on: vi.fn((event: string, callback: (...args: any[]) => void) => {
|
|
handlers[event] = callback;
|
|
return socketStub;
|
|
}),
|
|
close: vi.fn()
|
|
};
|
|
ioMock.mockReturnValue(socketStub);
|
|
});
|
|
|
|
it('skips socket connection when there is no auth session token', async () => {
|
|
getPlatformAccessTokenMock.mockRejectedValue(new Error('Not authenticated'));
|
|
|
|
const { result } = renderHook(() => useWebSocket('http://localhost:5000'));
|
|
|
|
await waitFor(() => {
|
|
expect(getPlatformAccessTokenMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
expect(ioMock).not.toHaveBeenCalled();
|
|
expect(result.current.connected).toBe(false);
|
|
expect(result.current.botState.settings.executionMode).toBe('Alerts');
|
|
});
|
|
|
|
it('connects with token and applies websocket event updates', async () => {
|
|
getPlatformAccessTokenMock.mockResolvedValue('token-abc');
|
|
|
|
const { result, unmount } = renderHook(() => useWebSocket('http://localhost:5000'));
|
|
|
|
await waitFor(() => {
|
|
expect(ioMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
const options = ioMock.mock.calls[0][1];
|
|
expect(options.auth.token).toBe('token-abc');
|
|
expect(options.transports).toEqual(['polling', 'websocket']);
|
|
|
|
act(() => handlers.connect());
|
|
expect(result.current.connected).toBe(true);
|
|
|
|
act(() => handlers.state({
|
|
symbols: {},
|
|
alerts: [],
|
|
positions: [],
|
|
orders: [],
|
|
history: [],
|
|
settings: {
|
|
executionMode: 'Paper',
|
|
riskPerTrade: 0.02,
|
|
totalCapital: 2000,
|
|
maxOpenTrades: 2,
|
|
isAlgoEnabled: true,
|
|
enabledRules: ['TrendBiasRule']
|
|
},
|
|
uptime: 10
|
|
}));
|
|
expect(result.current.botState.settings.executionMode).toBe('Paper');
|
|
expect(result.current.botState.settings.isAlgoEnabled).toBe(true);
|
|
|
|
act(() => handlers.symbol_update({
|
|
symbol: 'BTC/USD',
|
|
data: {
|
|
price: 70000,
|
|
change24h: 1.2,
|
|
changeToday: 0.4,
|
|
session: 'NY',
|
|
volatility: 'High',
|
|
signal: 'BUY',
|
|
priceHistory: [],
|
|
rules: {},
|
|
indicators: {}
|
|
}
|
|
}));
|
|
expect(result.current.botState.symbols['BTC/USD'].price).toBe(70000);
|
|
|
|
act(() => handlers.new_alert({
|
|
timestamp: 1,
|
|
type: 'info',
|
|
symbol: 'BTC/USD',
|
|
message: 'alert'
|
|
}));
|
|
expect(result.current.botState.alerts).toHaveLength(1);
|
|
|
|
act(() => handlers.positions_update([{
|
|
id: 'pos-1',
|
|
symbol: 'BTC/USD',
|
|
side: 'BUY',
|
|
size: 1,
|
|
entryPrice: 70000,
|
|
currentPrice: 70100,
|
|
stopLoss: 69000,
|
|
takeProfit: 71000,
|
|
unrealizedPnl: 100,
|
|
unrealizedPnlPercent: 0.14,
|
|
marketValue: 70100
|
|
}]));
|
|
expect(result.current.botState.positions).toHaveLength(1);
|
|
|
|
act(() => handlers.orders_update([{
|
|
id: 'ord-1',
|
|
symbol: 'BTC/USD',
|
|
type: 'market',
|
|
side: 'buy',
|
|
qty: 1,
|
|
price: 70000,
|
|
status: 'filled',
|
|
timestamp: 2
|
|
}]));
|
|
expect(result.current.botState.orders).toHaveLength(1);
|
|
|
|
act(() => handlers.history_update({
|
|
symbol: 'BTC/USD',
|
|
side: 'BUY',
|
|
entryPrice: 70000,
|
|
exitPrice: 71000,
|
|
size: 1,
|
|
pnl: 1000,
|
|
pnlPercent: 1.42,
|
|
reason: 'tp',
|
|
timestamp: 3
|
|
}));
|
|
expect(result.current.botState.history).toHaveLength(1);
|
|
|
|
act(() => handlers.settings_update({
|
|
executionMode: 'Live',
|
|
riskPerTrade: 0.01,
|
|
totalCapital: 5000,
|
|
maxOpenTrades: 5,
|
|
isAlgoEnabled: false,
|
|
enabledRules: []
|
|
}));
|
|
expect(result.current.botState.settings.executionMode).toBe('Live');
|
|
|
|
act(() => handlers.disconnect());
|
|
expect(result.current.connected).toBe(false);
|
|
|
|
unmount();
|
|
expect(socketStub.close).toHaveBeenCalled();
|
|
});
|
|
});
|