stock-bot/apps/intelligence-services/strategy-orchestrator/src/tests/execution/StrategyExecutionService.test.ts

237 lines
6.9 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach, jest } from 'bun:test';
import { EventEmitter } from 'events';
import { WebSocket } from 'ws';
import { StrategyExecutionService } from '../../core/execution/StrategyExecutionService';
import { StrategyRegistry } from '../../core/strategies/StrategyRegistry';
import { MarketDataFeed } from '../../core/backtesting/MarketDataFeed';
import { BaseStrategy, BarData, Order } from '../../core/Strategy';
// Mock WebSocket to avoid actual network connections during tests
jest.mock('ws', () => {
const EventEmitter = require('events');
class MockWebSocket extends EventEmitter {
static OPEN = 1;
readyState = 1;
close = jest.fn();
send = jest.fn();
}
class MockServer extends EventEmitter {
clients = new Set();
constructor() {
super();
// Add a mock client to the set
const mockClient = new MockWebSocket();
this.clients.add(mockClient);
}
close(callback) {
callback();
}
}
return {
WebSocket: MockWebSocket,
Server: MockServer
};
});
// Mock MarketDataFeed to avoid actual API calls
jest.mock('../../core/backtesting/MarketDataFeed', () => {
return {
MarketDataFeed: class {
async getHistoricalData(symbol, resolution, startDate, endDate) {
// Return mock data
return [
{
symbol,
timestamp: new Date(),
open: 100,
high: 105,
low: 95,
close: 102,
volume: 1000
}
];
}
}
};
});
// Mock strategy for testing
class MockStrategy extends BaseStrategy {
name = 'MockStrategy';
description = 'A mock strategy for testing';
symbols = ['AAPL', 'MSFT'];
parameters = { param1: 1, param2: 2 };
constructor(id: string) {
super(id);
}
async start(): Promise<void> {}
async stop(): Promise<void> {}
onBar(bar: BarData) {
// Return a mock signal
return {
action: 'BUY',
symbol: bar.symbol,
price: bar.close,
quantity: 10,
metadata: { reason: 'Test signal' }
};
}
async onOrderFilled(order: Order): Promise<void> {}
}
// Mock StrategyRegistry
jest.mock('../../core/strategies/StrategyRegistry', () => {
const mockInstance = {
getStrategyById: jest.fn(),
getStrategyTypes: () => [{ id: 'mock-strategy', name: 'Mock Strategy' }],
getAllStrategies: () => [new MockStrategy('mock-1')]
};
return {
StrategyRegistry: {
getInstance: () => mockInstance
}
};
});
describe('StrategyExecutionService', () => {
let executionService: StrategyExecutionService;
let strategyRegistry: typeof StrategyRegistry.getInstance;
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Create a new execution service for each test
executionService = new StrategyExecutionService('http://localhost:3001/api', 8082);
strategyRegistry = StrategyRegistry.getInstance();
// Setup mock strategy
const mockStrategy = new MockStrategy('test-strategy');
(strategyRegistry.getStrategyById as jest.Mock).mockReturnValue(mockStrategy);
});
afterEach(() => {
executionService.shutdown();
});
it('should initialize correctly', () => {
expect(executionService).toBeDefined();
});
it('should start a strategy correctly', () => {
// Arrange & Act
const result = executionService.startStrategy('test-strategy');
// Assert
expect(result).toBe(true);
expect(strategyRegistry.getStrategyById).toHaveBeenCalledWith('test-strategy');
// Check if WebSocket broadcast happened
const ws = executionService['webSocketServer'].clients.values().next().value;
expect(ws.send).toHaveBeenCalled();
// Check the broadcast message contains the correct type
const lastCall = ws.send.mock.calls[0][0];
const message = JSON.parse(lastCall);
expect(message.type).toBe('strategy_started');
expect(message.data.strategyId).toBe('test-strategy');
});
it('should stop a strategy correctly', () => {
// Arrange
executionService.startStrategy('test-strategy');
// Act
const result = executionService.stopStrategy('test-strategy');
// Assert
expect(result).toBe(true);
// Check if WebSocket broadcast happened
const ws = executionService['webSocketServer'].clients.values().next().value;
const lastCallIndex = ws.send.mock.calls.length - 1;
const lastCall = ws.send.mock.calls[lastCallIndex][0];
const message = JSON.parse(lastCall);
expect(message.type).toBe('strategy_stopped');
expect(message.data.strategyId).toBe('test-strategy');
});
it('should pause a strategy correctly', () => {
// Arrange
executionService.startStrategy('test-strategy');
// Act
const result = executionService.pauseStrategy('test-strategy');
// Assert
expect(result).toBe(true);
// Check if WebSocket broadcast happened
const ws = executionService['webSocketServer'].clients.values().next().value;
const lastCallIndex = ws.send.mock.calls.length - 1;
const lastCall = ws.send.mock.calls[lastCallIndex][0];
const message = JSON.parse(lastCall);
expect(message.type).toBe('strategy_paused');
expect(message.data.strategyId).toBe('test-strategy');
});
it('should process market data and generate signals', async () => {
// Arrange
executionService.startStrategy('test-strategy');
// Act - Trigger market data polling manually
await executionService['pollMarketData']('test-strategy');
// Assert - Check if signal was generated and broadcast
const ws = executionService['webSocketServer'].clients.values().next().value;
// Find the strategy_signal message
const signalMessages = ws.send.mock.calls
.map(call => JSON.parse(call[0]))
.filter(msg => msg.type === 'strategy_signal');
expect(signalMessages.length).toBeGreaterThan(0);
expect(signalMessages[0].data.action).toBe('BUY');
expect(signalMessages[0].data.strategyId).toBe('test-strategy');
});
it('should handle WebSocket client connections', () => {
// Arrange
const mockWs = new WebSocket();
const mockMessage = JSON.stringify({ type: 'get_active_strategies' });
// Act - Simulate connection and message
executionService['webSocketServer'].emit('connection', mockWs);
mockWs.emit('message', mockMessage);
// Assert
expect(mockWs.send).toHaveBeenCalled();
// Check that the response is a strategy_status_list message
const lastCall = mockWs.send.mock.calls[0][0];
const message = JSON.parse(lastCall);
expect(message.type).toBe('strategy_status_list');
});
it('should shut down correctly', () => {
// Act
executionService.shutdown();
// Assert - WebSocket server should be closed
const ws = executionService['webSocketServer'].clients.values().next().value;
expect(ws.close).toHaveBeenCalled();
});
});