Files
trading_bot_v4/tests/integration/orders/exit-orders-validation.test.ts
mindesbunister d637aac2d7 feat: Deploy HA auto-failover with database promotion
- Enhanced DNS failover monitor on secondary (72.62.39.24)
- Auto-promotes database: pg_ctl promote on failover
- Creates DEMOTED flag on primary via SSH (split-brain protection)
- Telegram notifications with database promotion status
- Startup safety script ready (integration pending)
- 90-second automatic recovery vs 10-30 min manual
- Zero-cost 95% enterprise HA benefit

Status: DEPLOYED and MONITORING (14:52 CET)
Next: Controlled failover test during maintenance
2025-12-12 15:54:03 +01:00

246 lines
7.6 KiB
TypeScript

/**
* Bug #76 Test: Stop-Loss Placement Validation
*
* Tests that placeExitOrders returns failure when SL signature is missing
* and logs critical errors appropriately.
*
* Created: Dec 9, 2025
* Reason: Bug #76 - placeExitOrders() can return SUCCESS with missing SL orders
*/
import { placeExitOrders, PlaceExitOrdersOptions } from '../../../lib/drift/orders'
// Mock dependencies
jest.mock('../../../lib/drift/client')
jest.mock('../../../lib/utils/logger')
jest.mock('../../../config/trading')
describe('Bug #76: Exit Orders Validation', () => {
let mockDriftClient: any
beforeEach(() => {
jest.clearAllMocks()
// Mock Drift service and client
mockDriftClient = {
placePerpOrder: jest.fn()
}
const { getDriftService } = require('../../../lib/drift/client')
getDriftService.mockReturnValue({
getClient: () => mockDriftClient
})
// Mock trading config
const { getMarketConfig } = require('../../../config/trading')
getMarketConfig.mockReturnValue({
driftMarketIndex: 0,
minOrderSize: 0.1
})
})
describe('Single Stop System', () => {
it('should return success when all 3 orders placed (TP1 + TP2 + SL)', async () => {
// Mock successful order placements
mockDriftClient.placePerpOrder
.mockResolvedValueOnce('TP1_SIG') // TP1
.mockResolvedValueOnce('TP2_SIG') // TP2
.mockResolvedValueOnce('SL_SIG') // SL
const options: PlaceExitOrdersOptions = {
symbol: 'SOL-PERP',
positionSizeUSD: 8000,
entryPrice: 140.00,
tp1Price: 141.20,
tp2Price: 142.41,
stopLossPrice: 138.71,
tp1SizePercent: 75,
tp2SizePercent: 25,
direction: 'long',
useDualStops: false
}
const result = await placeExitOrders(options)
console.log('RESULT success case', result)
expect(result.success).toBe(true)
expect(result.signatures).toHaveLength(3)
expect(result.signatures).toEqual(['TP1_SIG', 'TP2_SIG', 'SL_SIG'])
expect(result.expectedOrders).toBe(3)
expect(result.placedOrders).toBe(3)
})
it('should return success when TP2 is trigger-only (no on-chain TP2)', async () => {
// Only TP1 + SL should be placed when tp2SizePercent is 0
mockDriftClient.placePerpOrder
.mockResolvedValueOnce('TP1_SIG') // TP1
.mockResolvedValueOnce('SL_SIG') // SL
const options: PlaceExitOrdersOptions = {
symbol: 'SOL-PERP',
positionSizeUSD: 8000,
entryPrice: 140.00,
tp1Price: 141.20,
tp2Price: 142.41,
stopLossPrice: 138.71,
tp1SizePercent: 75,
tp2SizePercent: 0,
direction: 'long',
useDualStops: false
}
const result = await placeExitOrders(options)
expect(result.success).toBe(true)
expect(result.signatures).toEqual(['TP1_SIG', 'SL_SIG'])
expect(result.expectedOrders).toBe(2)
expect(result.placedOrders).toBe(2)
})
it('should return failure when SL placement fails', async () => {
// Mock TP1 and TP2 success, but SL fails
mockDriftClient.placePerpOrder
.mockResolvedValueOnce('TP1_SIG') // TP1
.mockResolvedValueOnce('TP2_SIG') // TP2
.mockRejectedValueOnce(new Error('Rate limit exceeded')) // SL fails
const options: PlaceExitOrdersOptions = {
symbol: 'SOL-PERP',
positionSizeUSD: 8000,
entryPrice: 140.00,
tp1Price: 141.20,
tp2Price: 142.41,
stopLossPrice: 138.71,
tp1SizePercent: 75,
tp2SizePercent: 25,
direction: 'long',
useDualStops: false
}
const result = await placeExitOrders(options)
expect(result.success).toBe(false)
expect(result.error).toContain('Stop loss placement failed')
})
})
describe('Dual Stop System', () => {
it('should return success when all 4 orders placed (TP1 + TP2 + Soft + Hard)', async () => {
// Mock successful order placements
mockDriftClient.placePerpOrder
.mockResolvedValueOnce('TP1_SIG') // TP1
.mockResolvedValueOnce('TP2_SIG') // TP2
.mockResolvedValueOnce('SOFT_SIG') // Soft Stop
.mockResolvedValueOnce('HARD_SIG') // Hard Stop
const options: PlaceExitOrdersOptions = {
symbol: 'SOL-PERP',
positionSizeUSD: 8000,
entryPrice: 140.00,
tp1Price: 141.20,
tp2Price: 142.41,
stopLossPrice: 138.71,
tp1SizePercent: 75,
tp2SizePercent: 25,
direction: 'long',
useDualStops: true,
softStopPrice: 139.00,
hardStopPrice: 138.50
}
const result = await placeExitOrders(options)
expect(result.success).toBe(true)
expect(result.signatures).toHaveLength(4)
expect(result.signatures).toEqual(['TP1_SIG', 'TP2_SIG', 'SOFT_SIG', 'HARD_SIG'])
expect(result.expectedOrders).toBe(4)
expect(result.placedOrders).toBe(4)
})
it('should return success with trigger-only TP2 (TP1 + Soft + Hard)', async () => {
mockDriftClient.placePerpOrder
.mockResolvedValueOnce('TP1_SIG') // TP1
.mockResolvedValueOnce('SOFT_SIG') // Soft Stop
.mockResolvedValueOnce('HARD_SIG') // Hard Stop
const options: PlaceExitOrdersOptions = {
symbol: 'SOL-PERP',
positionSizeUSD: 8000,
entryPrice: 140.00,
tp1Price: 141.20,
tp2Price: 142.41,
stopLossPrice: 138.71,
tp1SizePercent: 75,
tp2SizePercent: 0,
direction: 'long',
useDualStops: true,
softStopPrice: 139.00,
hardStopPrice: 138.50
}
const result = await placeExitOrders(options)
expect(result.success).toBe(true)
expect(result.signatures).toEqual(['TP1_SIG', 'SOFT_SIG', 'HARD_SIG'])
expect(result.expectedOrders).toBe(3)
expect(result.placedOrders).toBe(3)
})
it('should return failure when soft stop fails', async () => {
mockDriftClient.placePerpOrder
.mockResolvedValueOnce('TP1_SIG') // TP1
.mockResolvedValueOnce('TP2_SIG') // TP2
.mockRejectedValueOnce(new Error('Soft stop failed')) // Soft Stop
const options: PlaceExitOrdersOptions = {
symbol: 'SOL-PERP',
positionSizeUSD: 8000,
entryPrice: 140.00,
tp1Price: 141.20,
tp2Price: 142.41,
stopLossPrice: 138.71,
tp1SizePercent: 75,
tp2SizePercent: 25,
direction: 'long',
useDualStops: true,
softStopPrice: 139.00,
hardStopPrice: 138.50
}
const result = await placeExitOrders(options)
expect(result.success).toBe(false)
expect(result.error).toContain('Soft stop placement failed')
})
it('should return failure when hard stop fails', async () => {
mockDriftClient.placePerpOrder
.mockResolvedValueOnce('TP1_SIG') // TP1
.mockResolvedValueOnce('TP2_SIG') // TP2
.mockResolvedValueOnce('SOFT_SIG') // Soft Stop
.mockRejectedValueOnce(new Error('Hard stop failed')) // Hard Stop
const options: PlaceExitOrdersOptions = {
symbol: 'SOL-PERP',
positionSizeUSD: 8000,
entryPrice: 140.00,
tp1Price: 141.20,
tp2Price: 142.41,
stopLossPrice: 138.71,
tp1SizePercent: 75,
tp2SizePercent: 25,
direction: 'long',
useDualStops: true,
softStopPrice: 139.00,
hardStopPrice: 138.50
}
const result = await placeExitOrders(options)
expect(result.success).toBe(false)
expect(result.error).toContain('Hard stop placement failed')
})
})
})