pmxtjs 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{src/BaseExchange.ts → dist/BaseExchange.d.ts} +4 -22
- package/dist/BaseExchange.js +30 -0
- package/dist/exchanges/Kalshi.d.ts +20 -0
- package/{src/exchanges/Kalshi.ts → dist/exchanges/Kalshi.js} +75 -114
- package/dist/exchanges/Polymarket.d.ts +38 -0
- package/{src/exchanges/Polymarket.ts → dist/exchanges/Polymarket.js} +105 -146
- package/{src/index.ts → dist/index.d.ts} +0 -1
- package/dist/index.js +20 -0
- package/{src/types.ts → dist/types.d.ts} +3 -17
- package/dist/types.js +5 -0
- package/package.json +9 -1
- package/readme.md +62 -0
- package/coverage/clover.xml +0 -334
- package/coverage/coverage-final.json +0 -4
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/pmxt/BaseExchange.ts.html +0 -256
- package/coverage/lcov-report/pmxt/exchanges/Kalshi.ts.html +0 -1132
- package/coverage/lcov-report/pmxt/exchanges/Polymarket.ts.html +0 -1456
- package/coverage/lcov-report/pmxt/exchanges/index.html +0 -131
- package/coverage/lcov-report/pmxt/index.html +0 -116
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/src/BaseExchange.ts.html +0 -256
- package/coverage/lcov-report/src/exchanges/Kalshi.ts.html +0 -1132
- package/coverage/lcov-report/src/exchanges/Polymarket.ts.html +0 -1456
- package/coverage/lcov-report/src/exchanges/index.html +0 -131
- package/coverage/lcov-report/src/index.html +0 -116
- package/coverage/lcov.info +0 -766
- package/examples/get_event_prices.ts +0 -37
- package/examples/historical_prices.ts +0 -117
- package/examples/orderbook.ts +0 -102
- package/examples/recent_trades.ts +0 -29
- package/examples/search_events.ts +0 -68
- package/examples/search_market.ts +0 -29
- package/jest.config.js +0 -11
- package/pmxt-0.1.0.tgz +0 -0
- package/test/exchanges/kalshi/ApiErrors.test.ts +0 -132
- package/test/exchanges/kalshi/EmptyResponse.test.ts +0 -44
- package/test/exchanges/kalshi/FetchAndNormalizeMarkets.test.ts +0 -56
- package/test/exchanges/kalshi/LiveApi.integration.test.ts +0 -40
- package/test/exchanges/kalshi/MarketHistory.test.ts +0 -185
- package/test/exchanges/kalshi/OrderBook.test.ts +0 -149
- package/test/exchanges/kalshi/SearchMarkets.test.ts +0 -174
- package/test/exchanges/kalshi/VolumeFallback.test.ts +0 -44
- package/test/exchanges/polymarket/DataValidation.test.ts +0 -271
- package/test/exchanges/polymarket/ErrorHandling.test.ts +0 -34
- package/test/exchanges/polymarket/FetchAndNormalizeMarkets.test.ts +0 -68
- package/test/exchanges/polymarket/GetMarketsBySlug.test.ts +0 -268
- package/test/exchanges/polymarket/LiveApi.integration.test.ts +0 -44
- package/test/exchanges/polymarket/MarketHistory.test.ts +0 -207
- package/test/exchanges/polymarket/OrderBook.test.ts +0 -167
- package/test/exchanges/polymarket/RequestParameters.test.ts +0 -39
- package/test/exchanges/polymarket/SearchMarkets.test.ts +0 -176
- package/test/exchanges/polymarket/TradeHistory.test.ts +0 -248
- package/tsconfig.json +0 -12
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { PolymarketExchange } from '../../../src/exchanges/Polymarket';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Polymarket Data Validation Test
|
|
6
|
-
*
|
|
7
|
-
* What: Tests handling of malformed, missing, or edge-case data from the API.
|
|
8
|
-
* Why: Real-world APIs can return unexpected data structures that must be handled gracefully.
|
|
9
|
-
* How: Mocks various edge cases and verifies robust parsing and fallback logic.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
jest.mock('axios');
|
|
13
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
14
|
-
|
|
15
|
-
describe('PolymarketExchange - Data Validation', () => {
|
|
16
|
-
let exchange: PolymarketExchange;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
exchange = new PolymarketExchange();
|
|
20
|
-
jest.clearAllMocks();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should handle stringified JSON in outcomes field', async () => {
|
|
24
|
-
mockedAxios.get.mockResolvedValue({
|
|
25
|
-
data: [{
|
|
26
|
-
id: 'event-1',
|
|
27
|
-
slug: 'test',
|
|
28
|
-
title: 'Test Event',
|
|
29
|
-
markets: [{
|
|
30
|
-
id: 'market-1',
|
|
31
|
-
question: 'Test?',
|
|
32
|
-
outcomes: '["Yes", "No"]', // Stringified
|
|
33
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
34
|
-
clobTokenIds: '["token1", "token2"]',
|
|
35
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
36
|
-
volume24hr: '100000'
|
|
37
|
-
}]
|
|
38
|
-
}]
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const markets = await exchange.fetchMarkets();
|
|
42
|
-
|
|
43
|
-
expect(markets[0].outcomes.length).toBe(2);
|
|
44
|
-
expect(markets[0].outcomes[0].label).toBe('Yes');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should handle array outcomes field', async () => {
|
|
48
|
-
mockedAxios.get.mockResolvedValue({
|
|
49
|
-
data: [{
|
|
50
|
-
id: 'event-1',
|
|
51
|
-
slug: 'test',
|
|
52
|
-
title: 'Test Event',
|
|
53
|
-
markets: [{
|
|
54
|
-
id: 'market-1',
|
|
55
|
-
question: 'Test?',
|
|
56
|
-
outcomes: ['Yes', 'No'], // Already parsed
|
|
57
|
-
outcomePrices: ['0.52', '0.48'],
|
|
58
|
-
clobTokenIds: ['token1', 'token2'],
|
|
59
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
60
|
-
volume24hr: '100000'
|
|
61
|
-
}]
|
|
62
|
-
}]
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const markets = await exchange.fetchMarkets();
|
|
66
|
-
|
|
67
|
-
expect(markets[0].outcomes.length).toBe(2);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('should handle malformed JSON in outcomes gracefully', async () => {
|
|
71
|
-
mockedAxios.get.mockResolvedValue({
|
|
72
|
-
data: [{
|
|
73
|
-
id: 'event-1',
|
|
74
|
-
slug: 'test',
|
|
75
|
-
title: 'Test Event',
|
|
76
|
-
markets: [{
|
|
77
|
-
id: 'market-1',
|
|
78
|
-
question: 'Test?',
|
|
79
|
-
outcomes: '{invalid json}',
|
|
80
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
81
|
-
clobTokenIds: '["token1", "token2"]',
|
|
82
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
83
|
-
volume24hr: '100000'
|
|
84
|
-
}]
|
|
85
|
-
}]
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
89
|
-
const markets = await exchange.fetchMarkets();
|
|
90
|
-
|
|
91
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
92
|
-
expect(markets[0].outcomes.length).toBe(0);
|
|
93
|
-
consoleSpy.mockRestore();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it('should handle missing volume fields with fallback', async () => {
|
|
97
|
-
mockedAxios.get.mockResolvedValue({
|
|
98
|
-
data: [{
|
|
99
|
-
id: 'event-1',
|
|
100
|
-
slug: 'test',
|
|
101
|
-
title: 'Test Event',
|
|
102
|
-
markets: [{
|
|
103
|
-
id: 'market-1',
|
|
104
|
-
question: 'Test?',
|
|
105
|
-
outcomes: '["Yes", "No"]',
|
|
106
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
107
|
-
clobTokenIds: '["token1", "token2"]',
|
|
108
|
-
endDate: '2025-12-31T00:00:00Z'
|
|
109
|
-
// Missing volume24hr and volume
|
|
110
|
-
}]
|
|
111
|
-
}]
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const markets = await exchange.fetchMarkets();
|
|
115
|
-
|
|
116
|
-
expect(markets[0].volume24h).toBe(0);
|
|
117
|
-
expect(markets[0].volume).toBe(0);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it('should handle alternative volume field names', async () => {
|
|
121
|
-
mockedAxios.get.mockResolvedValue({
|
|
122
|
-
data: [{
|
|
123
|
-
id: 'event-1',
|
|
124
|
-
slug: 'test',
|
|
125
|
-
title: 'Test Event',
|
|
126
|
-
markets: [{
|
|
127
|
-
id: 'market-1',
|
|
128
|
-
question: 'Test?',
|
|
129
|
-
outcomes: '["Yes", "No"]',
|
|
130
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
131
|
-
clobTokenIds: '["token1", "token2"]',
|
|
132
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
133
|
-
volume_24h: '50000' // Alternative field name
|
|
134
|
-
}]
|
|
135
|
-
}]
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const markets = await exchange.fetchMarkets();
|
|
139
|
-
|
|
140
|
-
expect(markets[0].volume24h).toBe(50000);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should handle missing liquidity with nested fallback', async () => {
|
|
144
|
-
mockedAxios.get.mockResolvedValue({
|
|
145
|
-
data: [{
|
|
146
|
-
id: 'event-1',
|
|
147
|
-
slug: 'test',
|
|
148
|
-
title: 'Test Event',
|
|
149
|
-
markets: [{
|
|
150
|
-
id: 'market-1',
|
|
151
|
-
question: 'Test?',
|
|
152
|
-
outcomes: '["Yes", "No"]',
|
|
153
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
154
|
-
clobTokenIds: '["token1", "token2"]',
|
|
155
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
156
|
-
volume24hr: '100000',
|
|
157
|
-
rewards: { liquidity: 25000 } // Nested liquidity
|
|
158
|
-
}]
|
|
159
|
-
}]
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const markets = await exchange.fetchMarkets();
|
|
163
|
-
|
|
164
|
-
expect(markets[0].liquidity).toBe(25000);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it('should handle candidate name extraction for Yes/No markets', async () => {
|
|
168
|
-
mockedAxios.get.mockResolvedValue({
|
|
169
|
-
data: [{
|
|
170
|
-
id: 'event-1',
|
|
171
|
-
slug: 'test',
|
|
172
|
-
title: 'Presidential Election',
|
|
173
|
-
markets: [{
|
|
174
|
-
id: 'market-1',
|
|
175
|
-
question: 'Will Trump win?',
|
|
176
|
-
groupItemTitle: 'Trump',
|
|
177
|
-
outcomes: '["Yes", "No"]',
|
|
178
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
179
|
-
clobTokenIds: '["token1", "token2"]',
|
|
180
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
181
|
-
volume24hr: '100000'
|
|
182
|
-
}]
|
|
183
|
-
}]
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
const markets = await exchange.fetchMarkets();
|
|
187
|
-
|
|
188
|
-
expect(markets[0].outcomes[0].label).toBe('Trump');
|
|
189
|
-
expect(markets[0].outcomes[1].label).toBe('Not Trump');
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('should handle multi-outcome markets', async () => {
|
|
193
|
-
mockedAxios.get.mockResolvedValue({
|
|
194
|
-
data: [{
|
|
195
|
-
id: 'event-1',
|
|
196
|
-
slug: 'test',
|
|
197
|
-
title: 'Winner Prediction',
|
|
198
|
-
markets: [{
|
|
199
|
-
id: 'market-1',
|
|
200
|
-
question: 'Who will win?',
|
|
201
|
-
outcomes: '["Trump", "Biden", "RFK Jr", "Other"]',
|
|
202
|
-
outcomePrices: '["0.40", "0.35", "0.15", "0.10"]',
|
|
203
|
-
clobTokenIds: '["token1", "token2", "token3", "token4"]',
|
|
204
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
205
|
-
volume24hr: '100000'
|
|
206
|
-
}]
|
|
207
|
-
}]
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const markets = await exchange.fetchMarkets();
|
|
211
|
-
|
|
212
|
-
expect(markets[0].outcomes.length).toBe(4);
|
|
213
|
-
expect(markets[0].outcomes[0].label).toBe('Trump');
|
|
214
|
-
expect(markets[0].outcomes[3].label).toBe('Other');
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('should handle missing endDate with fallback', async () => {
|
|
218
|
-
mockedAxios.get.mockResolvedValue({
|
|
219
|
-
data: [{
|
|
220
|
-
id: 'event-1',
|
|
221
|
-
slug: 'test',
|
|
222
|
-
title: 'Test Event',
|
|
223
|
-
markets: [{
|
|
224
|
-
id: 'market-1',
|
|
225
|
-
question: 'Test?',
|
|
226
|
-
outcomes: '["Yes", "No"]',
|
|
227
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
228
|
-
clobTokenIds: '["token1", "token2"]',
|
|
229
|
-
volume24hr: '100000'
|
|
230
|
-
// Missing endDate
|
|
231
|
-
}]
|
|
232
|
-
}]
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
const markets = await exchange.fetchMarkets();
|
|
236
|
-
|
|
237
|
-
expect(markets[0].resolutionDate).toBeInstanceOf(Date);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
it('should skip events without markets', async () => {
|
|
241
|
-
mockedAxios.get.mockResolvedValue({
|
|
242
|
-
data: [
|
|
243
|
-
{
|
|
244
|
-
id: 'event-1',
|
|
245
|
-
slug: 'test',
|
|
246
|
-
title: 'Test Event'
|
|
247
|
-
// Missing markets field
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
id: 'event-2',
|
|
251
|
-
slug: 'test2',
|
|
252
|
-
title: 'Test Event 2',
|
|
253
|
-
markets: [{
|
|
254
|
-
id: 'market-1',
|
|
255
|
-
question: 'Test?',
|
|
256
|
-
outcomes: '["Yes", "No"]',
|
|
257
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
258
|
-
clobTokenIds: '["token1", "token2"]',
|
|
259
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
260
|
-
volume24hr: '100000'
|
|
261
|
-
}]
|
|
262
|
-
}
|
|
263
|
-
]
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const markets = await exchange.fetchMarkets();
|
|
267
|
-
|
|
268
|
-
expect(markets.length).toBe(1);
|
|
269
|
-
expect(markets[0].id).toBe('market-1');
|
|
270
|
-
});
|
|
271
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { PolymarketExchange } from '../../../src/exchanges/Polymarket';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Polymarket Error Handling Test
|
|
6
|
-
*
|
|
7
|
-
* What: Tests how the Polymarket exchange handle API errors and network failures.
|
|
8
|
-
* Why: To ensure application stability when external services are unavailable.
|
|
9
|
-
* How: Mocks a rejected axios promise and verifies that the exchange returns an empty
|
|
10
|
-
* array instead of throwing, and logs the error.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
jest.mock('axios');
|
|
14
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
15
|
-
|
|
16
|
-
describe('PolymarketExchange - Error Handling', () => {
|
|
17
|
-
let exchange: PolymarketExchange;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
exchange = new PolymarketExchange();
|
|
21
|
-
jest.clearAllMocks();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should handle API errors by returning an empty list', async () => {
|
|
25
|
-
mockedAxios.get.mockRejectedValue(new Error('Network Error'));
|
|
26
|
-
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
27
|
-
|
|
28
|
-
const markets = await exchange.fetchMarkets();
|
|
29
|
-
|
|
30
|
-
expect(markets).toEqual([]);
|
|
31
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
32
|
-
consoleSpy.mockRestore();
|
|
33
|
-
});
|
|
34
|
-
});
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { PolymarketExchange } from '../../../src/exchanges/Polymarket';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Polymarket Markets Normalization Test
|
|
6
|
-
*
|
|
7
|
-
* What: Tests the normalization of Polymarket's Gamma API response.
|
|
8
|
-
* Why: Polymarket's API often returns outcomes and prices as stringified JSON.
|
|
9
|
-
* We need to ensure these are correctly parsed regardless of whether
|
|
10
|
-
* the API returns them as strings or actual arrays.
|
|
11
|
-
* How: Mocks various response formats from the Gamma API and validates the UnifiedMarket output.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
jest.mock('axios');
|
|
15
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
16
|
-
|
|
17
|
-
describe('PolymarketExchange - Fetch and Normalize Markets', () => {
|
|
18
|
-
let exchange: PolymarketExchange;
|
|
19
|
-
|
|
20
|
-
beforeEach(() => {
|
|
21
|
-
exchange = new PolymarketExchange();
|
|
22
|
-
jest.clearAllMocks();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const mockGammaResponse = [
|
|
26
|
-
{
|
|
27
|
-
id: "eventId1",
|
|
28
|
-
title: "Presidential Election 2024",
|
|
29
|
-
markets: [
|
|
30
|
-
{
|
|
31
|
-
id: "marketId1",
|
|
32
|
-
question: "Winner",
|
|
33
|
-
volume_24h: 1000000,
|
|
34
|
-
outcomePrices: "[\"0.60\", \"0.40\"]", // Stringified JSON
|
|
35
|
-
outcomes: "[\"Candidate A\", \"Candidate B\"]", // Stringified JSON
|
|
36
|
-
rewards: { liquidity: 50000 }
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: "marketId2",
|
|
40
|
-
question: "Runner Up",
|
|
41
|
-
outcomes: ["A", "B"], // Actual array
|
|
42
|
-
outcomePrices: ["0.1", "0.9"], // Actual array
|
|
43
|
-
volume24hr: "500" // Alternative field name
|
|
44
|
-
}
|
|
45
|
-
]
|
|
46
|
-
}
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
it('should correctly parse stringified and non-stringified outcomes/prices', async () => {
|
|
50
|
-
mockedAxios.get.mockResolvedValue({ data: mockGammaResponse });
|
|
51
|
-
|
|
52
|
-
const markets = await exchange.fetchMarkets();
|
|
53
|
-
|
|
54
|
-
expect(markets).toHaveLength(2);
|
|
55
|
-
|
|
56
|
-
// Check Market 1 (Stringified)
|
|
57
|
-
const m1 = markets[0];
|
|
58
|
-
expect(m1.id).toBe("marketId1");
|
|
59
|
-
expect(m1.outcomes[0].label).toBe("Candidate A");
|
|
60
|
-
expect(m1.outcomes[0].price).toBe(0.60);
|
|
61
|
-
|
|
62
|
-
// Check Market 2 (Array)
|
|
63
|
-
const m2 = markets[1];
|
|
64
|
-
expect(m2.id).toBe("marketId2");
|
|
65
|
-
expect(m2.outcomes[0].label).toBe("A");
|
|
66
|
-
expect(m2.volume24h).toBe(500);
|
|
67
|
-
});
|
|
68
|
-
});
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
import { PolymarketExchange } from '../../../src/exchanges/Polymarket';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Polymarket getMarketsBySlug() Test
|
|
6
|
-
*
|
|
7
|
-
* What: Tests fetching specific markets by their event slug (from URL).
|
|
8
|
-
* Why: This is a CRITICAL user-facing feature for deep-linking to specific events.
|
|
9
|
-
* How: Mocks Gamma API responses for slug-based queries and verifies data extraction.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
jest.mock('axios');
|
|
13
|
-
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
|
14
|
-
|
|
15
|
-
describe('PolymarketExchange - getMarketsBySlug', () => {
|
|
16
|
-
let exchange: PolymarketExchange;
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
exchange = new PolymarketExchange();
|
|
20
|
-
jest.clearAllMocks();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('should fetch markets by slug successfully', async () => {
|
|
24
|
-
mockedAxios.get.mockResolvedValue({
|
|
25
|
-
data: [{
|
|
26
|
-
id: 'event-1',
|
|
27
|
-
slug: 'fed-rate-decision',
|
|
28
|
-
title: 'Federal Reserve Rate Decision',
|
|
29
|
-
description: 'Will the Fed cut rates?',
|
|
30
|
-
markets: [{
|
|
31
|
-
id: 'market-1',
|
|
32
|
-
question: 'Rate cut in March?',
|
|
33
|
-
description: 'Federal Reserve rate decision',
|
|
34
|
-
outcomes: '["Yes", "No"]',
|
|
35
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
36
|
-
clobTokenIds: '["token1", "token2"]',
|
|
37
|
-
endDate: '2025-03-31T00:00:00Z',
|
|
38
|
-
volume24hr: '100000',
|
|
39
|
-
volume: '500000',
|
|
40
|
-
liquidity: '50000'
|
|
41
|
-
}]
|
|
42
|
-
}]
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const markets = await exchange.getMarketsBySlug('fed-rate-decision');
|
|
46
|
-
|
|
47
|
-
expect(markets.length).toBe(1);
|
|
48
|
-
expect(markets[0].id).toBe('market-1');
|
|
49
|
-
expect(markets[0].title).toBe('Federal Reserve Rate Decision');
|
|
50
|
-
expect(markets[0].outcomes.length).toBe(2);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should handle empty events array', async () => {
|
|
54
|
-
mockedAxios.get.mockResolvedValue({
|
|
55
|
-
data: []
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const markets = await exchange.getMarketsBySlug('nonexistent-slug');
|
|
59
|
-
|
|
60
|
-
expect(markets).toEqual([]);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('should handle null events', async () => {
|
|
64
|
-
mockedAxios.get.mockResolvedValue({
|
|
65
|
-
data: null
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const markets = await exchange.getMarketsBySlug('invalid-slug');
|
|
69
|
-
|
|
70
|
-
expect(markets).toEqual([]);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('should skip events without markets', async () => {
|
|
74
|
-
mockedAxios.get.mockResolvedValue({
|
|
75
|
-
data: [{
|
|
76
|
-
id: 'event-1',
|
|
77
|
-
slug: 'test-event',
|
|
78
|
-
title: 'Test Event'
|
|
79
|
-
// Missing markets field
|
|
80
|
-
}]
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const markets = await exchange.getMarketsBySlug('test-event');
|
|
84
|
-
|
|
85
|
-
expect(markets).toEqual([]);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should handle candidate name extraction', async () => {
|
|
89
|
-
mockedAxios.get.mockResolvedValue({
|
|
90
|
-
data: [{
|
|
91
|
-
id: 'event-1',
|
|
92
|
-
slug: 'election-2024',
|
|
93
|
-
title: 'Presidential Election',
|
|
94
|
-
markets: [{
|
|
95
|
-
id: 'market-1',
|
|
96
|
-
question: 'Will Trump win?',
|
|
97
|
-
groupItemTitle: 'Trump',
|
|
98
|
-
outcomes: '["Yes", "No"]',
|
|
99
|
-
outcomePrices: '["0.48", "0.52"]',
|
|
100
|
-
clobTokenIds: '["token1", "token2"]',
|
|
101
|
-
endDate: '2024-11-05T00:00:00Z',
|
|
102
|
-
volume24hr: '1000000'
|
|
103
|
-
}]
|
|
104
|
-
}]
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const markets = await exchange.getMarketsBySlug('election-2024');
|
|
108
|
-
|
|
109
|
-
expect(markets[0].outcomes[0].label).toBe('Trump');
|
|
110
|
-
expect(markets[0].outcomes[1].label).toBe('Not Trump');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should fallback to question for candidate name', async () => {
|
|
114
|
-
mockedAxios.get.mockResolvedValue({
|
|
115
|
-
data: [{
|
|
116
|
-
id: 'event-1',
|
|
117
|
-
slug: 'test',
|
|
118
|
-
title: 'Test',
|
|
119
|
-
markets: [{
|
|
120
|
-
id: 'market-1',
|
|
121
|
-
question: 'Biden',
|
|
122
|
-
outcomes: '["Yes", "No"]',
|
|
123
|
-
outcomePrices: '["0.50", "0.50"]',
|
|
124
|
-
clobTokenIds: '["token1", "token2"]',
|
|
125
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
126
|
-
volume24hr: '10000'
|
|
127
|
-
}]
|
|
128
|
-
}]
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
const markets = await exchange.getMarketsBySlug('test');
|
|
132
|
-
|
|
133
|
-
expect(markets[0].outcomes[0].label).toBe('Biden');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should handle JSON parsing errors gracefully', async () => {
|
|
137
|
-
mockedAxios.get.mockResolvedValue({
|
|
138
|
-
data: [{
|
|
139
|
-
id: 'event-1',
|
|
140
|
-
slug: 'test',
|
|
141
|
-
title: 'Test Event',
|
|
142
|
-
markets: [{
|
|
143
|
-
id: 'market-1',
|
|
144
|
-
question: 'Test?',
|
|
145
|
-
outcomes: '{invalid json}',
|
|
146
|
-
outcomePrices: '["0.50", "0.50"]',
|
|
147
|
-
clobTokenIds: '["token1", "token2"]',
|
|
148
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
149
|
-
volume24hr: '10000'
|
|
150
|
-
}]
|
|
151
|
-
}]
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
155
|
-
const markets = await exchange.getMarketsBySlug('test');
|
|
156
|
-
|
|
157
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
158
|
-
expect(markets[0].outcomes.length).toBe(0);
|
|
159
|
-
consoleSpy.mockRestore();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should handle price change data', async () => {
|
|
163
|
-
mockedAxios.get.mockResolvedValue({
|
|
164
|
-
data: [{
|
|
165
|
-
id: 'event-1',
|
|
166
|
-
slug: 'test',
|
|
167
|
-
title: 'Test',
|
|
168
|
-
markets: [{
|
|
169
|
-
id: 'market-1',
|
|
170
|
-
question: 'Test?',
|
|
171
|
-
outcomes: '["Yes", "No"]',
|
|
172
|
-
outcomePrices: '["0.52", "0.48"]',
|
|
173
|
-
clobTokenIds: '["token1", "token2"]',
|
|
174
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
175
|
-
volume24hr: '10000',
|
|
176
|
-
oneDayPriceChange: 0.05
|
|
177
|
-
}]
|
|
178
|
-
}]
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const markets = await exchange.getMarketsBySlug('test');
|
|
182
|
-
|
|
183
|
-
expect(markets[0].outcomes[0].priceChange24h).toBe(0.05);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('should handle missing endDate with fallback', async () => {
|
|
187
|
-
mockedAxios.get.mockResolvedValue({
|
|
188
|
-
data: [{
|
|
189
|
-
id: 'event-1',
|
|
190
|
-
slug: 'test',
|
|
191
|
-
title: 'Test',
|
|
192
|
-
markets: [{
|
|
193
|
-
id: 'market-1',
|
|
194
|
-
question: 'Test?',
|
|
195
|
-
outcomes: '["Yes", "No"]',
|
|
196
|
-
outcomePrices: '["0.50", "0.50"]',
|
|
197
|
-
clobTokenIds: '["token1", "token2"]',
|
|
198
|
-
volume24hr: '10000'
|
|
199
|
-
// Missing endDate
|
|
200
|
-
}]
|
|
201
|
-
}]
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
const markets = await exchange.getMarketsBySlug('test');
|
|
205
|
-
|
|
206
|
-
expect(markets[0].resolutionDate).toBeInstanceOf(Date);
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('should handle API errors gracefully', async () => {
|
|
210
|
-
mockedAxios.get.mockRejectedValue(new Error('API Error'));
|
|
211
|
-
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
212
|
-
|
|
213
|
-
const markets = await exchange.getMarketsBySlug('test');
|
|
214
|
-
|
|
215
|
-
expect(markets).toEqual([]);
|
|
216
|
-
expect(consoleSpy).toHaveBeenCalled();
|
|
217
|
-
consoleSpy.mockRestore();
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('should include image data', async () => {
|
|
221
|
-
mockedAxios.get.mockResolvedValue({
|
|
222
|
-
data: [{
|
|
223
|
-
id: 'event-1',
|
|
224
|
-
slug: 'test',
|
|
225
|
-
title: 'Test',
|
|
226
|
-
image: 'https://example.com/image.jpg',
|
|
227
|
-
markets: [{
|
|
228
|
-
id: 'market-1',
|
|
229
|
-
question: 'Test?',
|
|
230
|
-
outcomes: '["Yes", "No"]',
|
|
231
|
-
outcomePrices: '["0.50", "0.50"]',
|
|
232
|
-
clobTokenIds: '["token1", "token2"]',
|
|
233
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
234
|
-
volume24hr: '10000'
|
|
235
|
-
}]
|
|
236
|
-
}]
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
const markets = await exchange.getMarketsBySlug('test');
|
|
240
|
-
|
|
241
|
-
expect(markets[0].image).toBe('https://example.com/image.jpg');
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it('should handle volume field variations', async () => {
|
|
245
|
-
mockedAxios.get.mockResolvedValue({
|
|
246
|
-
data: [{
|
|
247
|
-
id: 'event-1',
|
|
248
|
-
slug: 'test',
|
|
249
|
-
title: 'Test',
|
|
250
|
-
markets: [{
|
|
251
|
-
id: 'market-1',
|
|
252
|
-
question: 'Test?',
|
|
253
|
-
outcomes: '["Yes", "No"]',
|
|
254
|
-
outcomePrices: '["0.50", "0.50"]',
|
|
255
|
-
clobTokenIds: '["token1", "token2"]',
|
|
256
|
-
endDate: '2025-12-31T00:00:00Z',
|
|
257
|
-
volume_24h: '25000', // Alternative field name
|
|
258
|
-
volume: '100000'
|
|
259
|
-
}]
|
|
260
|
-
}]
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
const markets = await exchange.getMarketsBySlug('test');
|
|
264
|
-
|
|
265
|
-
expect(markets[0].volume24h).toBe(25000);
|
|
266
|
-
expect(markets[0].volume).toBe(100000);
|
|
267
|
-
});
|
|
268
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { PolymarketExchange } from '../../../src/exchanges/Polymarket';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Polymarket Integration Test (Live API)
|
|
5
|
-
*
|
|
6
|
-
* What: Verifies real-world connectivity and data structure from Polymarket's Gamma API.
|
|
7
|
-
* Why: Polymarket data is complex and frequently changes; live tests catch schema drift.
|
|
8
|
-
* How: Makes actual HTTP requests to Polymarket and validates the properties
|
|
9
|
-
* of the returned UnifiedMarket objects.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
describe('PolymarketExchange - Live API Integration', () => {
|
|
13
|
-
const exchange = new PolymarketExchange();
|
|
14
|
-
jest.setTimeout(15000);
|
|
15
|
-
|
|
16
|
-
it('should fetch real active markets with correct schema', async () => {
|
|
17
|
-
const markets = await exchange.fetchMarkets({ limit: 5 });
|
|
18
|
-
|
|
19
|
-
expect(markets.length).toBeGreaterThan(0);
|
|
20
|
-
|
|
21
|
-
const m = markets[0];
|
|
22
|
-
expect(m.id).toBeDefined();
|
|
23
|
-
expect(m.title.length).toBeGreaterThan(0);
|
|
24
|
-
expect(m.outcomes.length).toBeGreaterThanOrEqual(2);
|
|
25
|
-
expect(m.url).toContain('polymarket.com');
|
|
26
|
-
expect(m.resolutionDate).toBeInstanceOf(Date);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should support pagination on live API', async () => {
|
|
30
|
-
const page1 = await exchange.fetchMarkets({ limit: 1, offset: 0 });
|
|
31
|
-
const page2 = await exchange.fetchMarkets({ limit: 1, offset: 1 });
|
|
32
|
-
|
|
33
|
-
if (page1.length > 0 && page2.length > 0) {
|
|
34
|
-
expect(page1[0].id).not.toBe(page2[0].id);
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should return top markets with non-zero volume', async () => {
|
|
39
|
-
const markets = await exchange.fetchMarkets({ limit: 3, sort: 'volume' });
|
|
40
|
-
if (markets.length > 0) {
|
|
41
|
-
expect(markets[0].volume24h).toBeGreaterThan(0);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
});
|