146 lines
4.3 KiB
TypeScript
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);
|
|
});
|
|
});
|