-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathbestRoute.test.ts
More file actions
120 lines (96 loc) · 3.33 KB
/
Copy pathbestRoute.test.ts
File metadata and controls
120 lines (96 loc) · 3.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { getBestRoute } from '../aggregator/bestRoute'
import { pgPool } from '../db'
import * as StellarSdk from '@stellar/stellar-sdk'
// Mock dependencies
vi.mock('../db', () => ({
pgPool: {
query: vi.fn()
}
}))
vi.mock('@stellar/stellar-sdk', async (importOriginal) => {
const actual = await importOriginal<typeof import('@stellar/stellar-sdk')>()
const callFn = vi.fn()
return {
...actual,
Horizon: {
Server: vi.fn(function() {
return {
strictSendPaths: vi.fn().mockReturnThis(),
call: callFn
}
})
},
Asset: Object.assign(
vi.fn(function(code, issuer) { return { code, issuer } }),
{ native: vi.fn(() => 'native') }
),
__mockCall: callFn
}
})
describe('getBestRoute', () => {
const assetA = { code: 'USDC', issuer: 'GA2C5RFPE6GCKIG3EQNCIMHO7OA7Q6M2XQ2UBNR5NCTU24VTY4A2J7B2' }
const assetB = { code: 'XLM', issuer: null }
const pairKey = 'USDC:XLM'
const mockQuery = vi.mocked(pgPool.query)
const mockCall = (StellarSdk as any).__mockCall
beforeEach(() => {
vi.clearAllMocks()
})
it('Case 1: returns SDEX when SDEX price is better', async () => {
// SDEX Price: 0.5
mockCall.mockResolvedValue({
records: [{ destination_amount: '500' }] // 500 / 1000 = 0.5
})
// AMM Price: 0.4
mockQuery.mockResolvedValue({
rows: [{ reserve_a: '10000', reserve_b: '4000', fee_bp: '30' }]
} as any)
const result = await getBestRoute(assetA, assetB, pairKey, 1000)
expect(result.route).toBe('SDEX')
expect(result.sdexPrice).toBe(0.5)
// precision handling
expect(result.ammPrice).toBeCloseTo(0.362644, 6)
})
it('Case 2: returns AMM when AMM price is better', async () => {
// SDEX Price: 0.4
mockCall.mockResolvedValue({
records: [{ destination_amount: '400' }]
})
// AMM Price: 0.5 (approx)
mockQuery.mockResolvedValue({
rows: [{ reserve_a: '10000', reserve_b: '5000', fee_bp: '30' }]
} as any)
const result = await getBestRoute(assetA, assetB, pairKey, 1000)
expect(result.route).toBe('AMM')
})
it('Case 3: Only one source available (SDEX only)', async () => {
// SDEX Price: 0.5
mockCall.mockResolvedValue({
records: [{ destination_amount: '500' }]
})
// AMM: No pool data
mockQuery.mockResolvedValue({ rows: [] } as any)
const result = await getBestRoute(assetA, assetB, pairKey, 1000)
expect(result.route).toBe('SDEX')
expect(result.ammPrice).toBe(0)
})
it('Case 4: No data available throws error', async () => {
// SDEX: no paths
mockCall.mockResolvedValue({ records: [] })
// AMM: No pool data
mockQuery.mockResolvedValue({ rows: [] } as any)
await expect(getBestRoute(assetA, assetB, pairKey, 1000))
.rejects.toThrow('No pricing data available')
})
it('Case 5: Spread / price calculation precision', async () => {
// Exact SDEX Price
mockCall.mockResolvedValue({
records: [{ destination_amount: '123.456789' }] // 123.456789 / 1000 = 0.123456789
})
// AMM: no pool data to simplify test or give known value
mockQuery.mockResolvedValue({ rows: [] } as any)
const result = await getBestRoute(assetA, assetB, pairKey, 1000)
expect(result.sdexPrice).toBeCloseTo(0.123457, 6)
})
})