overtime-live-trading-utils 2.1.45 → 3.0.0-rc.1
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/.circleci/config.yml +32 -32
- package/.prettierrc +9 -9
- package/codecov.yml +20 -20
- package/index.ts +26 -26
- package/jest.config.ts +16 -16
- package/main.js +1 -1
- package/package.json +30 -30
- package/src/constants/common.ts +8 -7
- package/src/constants/errors.ts +7 -6
- package/src/constants/sports.ts +78 -78
- package/src/enums/sports.ts +109 -109
- package/src/tests/mock/MockLeagueMap.ts +225 -170
- package/src/tests/mock/MockOpticOddsEvents.ts +662 -662
- package/src/tests/mock/MockOpticSoccer.ts +9864 -9378
- package/src/tests/mock/MockSoccerRedis.ts +2308 -2308
- package/src/tests/mock/OpticOddsMock/MockNBA.ts +17269 -0
- package/src/tests/mock/OpticOddsMock/MockRedisNba.ts +21 -0
- package/src/tests/unit/bookmakers.test.ts +149 -79
- package/src/tests/unit/markets.test.ts +177 -156
- package/src/tests/unit/odds.test.ts +104 -92
- package/src/tests/unit/resolution.test.ts +1489 -1489
- package/src/tests/unit/sports.test.ts +58 -58
- package/src/tests/unit/spread.test.ts +145 -131
- package/src/tests/utils/helper.ts +11 -0
- package/src/types/bookmakers.ts +7 -0
- package/src/types/missing-types.d.ts +2 -2
- package/src/types/odds.ts +80 -61
- package/src/types/resolution.ts +656 -660
- package/src/types/sports.ts +22 -19
- package/src/utils/bookmakers.ts +309 -159
- package/src/utils/constraints.ts +210 -210
- package/src/utils/gameMatching.ts +81 -81
- package/src/utils/markets.ts +120 -119
- package/src/utils/odds.ts +952 -918
- package/src/utils/opticOdds.ts +71 -71
- package/src/utils/resolution.ts +319 -319
- package/src/utils/sportPeriodMapping.ts +36 -36
- package/src/utils/sports.ts +51 -51
- package/src/utils/spread.ts +97 -97
- package/tsconfig.json +16 -16
- package/webpack.config.js +24 -24
- package/CLAUDE.md +0 -84
- package/resolution_live_markets.md +0 -356
|
@@ -1,1489 +1,1489 @@
|
|
|
1
|
-
import {
|
|
2
|
-
detectCompletedPeriods,
|
|
3
|
-
canResolveMarketsForEvent,
|
|
4
|
-
canResolveMultipleTypeIdsForEvent,
|
|
5
|
-
filterMarketsThatCanBeResolved,
|
|
6
|
-
} from '../../utils/resolution';
|
|
7
|
-
import { SportPeriodType } from '../../types/resolution';
|
|
8
|
-
import {
|
|
9
|
-
MockSoccerCompletedEvent,
|
|
10
|
-
MockSoccerLiveSecondHalf,
|
|
11
|
-
MockSoccerLiveFirstHalf,
|
|
12
|
-
MockNFLCompletedEvent,
|
|
13
|
-
MockNFLLiveThirdQuarter,
|
|
14
|
-
MockMLBCompletedEvent,
|
|
15
|
-
MockMLBLiveSixthInning,
|
|
16
|
-
MockSoccerLiveFirstHalfInProgress,
|
|
17
|
-
MockNFLCompletedWithOvertime,
|
|
18
|
-
MockNBACompletedEvent,
|
|
19
|
-
MockNBALiveAtHalftime,
|
|
20
|
-
} from '../mock/MockOpticOddsEvents';
|
|
21
|
-
|
|
22
|
-
describe('Resolution Utils', () => {
|
|
23
|
-
describe('detectCompletedPeriods', () => {
|
|
24
|
-
it('Should detect completed periods for real completed soccer game (UEFA Europa League)', () => {
|
|
25
|
-
const result = detectCompletedPeriods(MockSoccerCompletedEvent);
|
|
26
|
-
|
|
27
|
-
expect(result).not.toBeNull();
|
|
28
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
29
|
-
expect(result?.readyForResolution).toBe(true);
|
|
30
|
-
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 0.0 });
|
|
31
|
-
expect(result?.periodScores['period2']).toEqual({ home: 1.0, away: 0.0 });
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('Should detect completed first half in real live soccer game (2nd half)', () => {
|
|
35
|
-
const result = detectCompletedPeriods(MockSoccerLiveSecondHalf);
|
|
36
|
-
|
|
37
|
-
expect(result).not.toBeNull();
|
|
38
|
-
expect(result?.completedPeriods).toEqual([1]);
|
|
39
|
-
expect(result?.readyForResolution).toBe(true);
|
|
40
|
-
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 1.0 });
|
|
41
|
-
expect(result?.currentPeriod).toBe(2);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('Should return null for real live soccer game in first half (no completed periods)', () => {
|
|
45
|
-
const result = detectCompletedPeriods(MockSoccerLiveFirstHalf);
|
|
46
|
-
|
|
47
|
-
expect(result).toBeNull();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('Should detect completed periods for real completed NFL game (Patriots vs Panthers)', () => {
|
|
51
|
-
const result = detectCompletedPeriods(MockNFLCompletedEvent);
|
|
52
|
-
|
|
53
|
-
expect(result).not.toBeNull();
|
|
54
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
55
|
-
expect(result?.readyForResolution).toBe(true);
|
|
56
|
-
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 6.0 });
|
|
57
|
-
expect(result?.periodScores['period2']).toEqual({ home: 21.0, away: 0.0 });
|
|
58
|
-
expect(result?.periodScores['period3']).toEqual({ home: 7.0, away: 0.0 });
|
|
59
|
-
expect(result?.periodScores['period4']).toEqual({ home: 7.0, away: 7.0 });
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('Should detect completed quarters in real live NFL game (3rd quarter)', () => {
|
|
63
|
-
const result = detectCompletedPeriods(MockNFLLiveThirdQuarter);
|
|
64
|
-
|
|
65
|
-
expect(result).not.toBeNull();
|
|
66
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
67
|
-
expect(result?.readyForResolution).toBe(true);
|
|
68
|
-
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
69
|
-
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
70
|
-
expect(result?.currentPeriod).toBe(3);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('Should detect completed innings for real completed MLB game (Tigers vs Guardians)', () => {
|
|
74
|
-
const result = detectCompletedPeriods(MockMLBCompletedEvent);
|
|
75
|
-
|
|
76
|
-
expect(result).not.toBeNull();
|
|
77
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
78
|
-
expect(result?.readyForResolution).toBe(true);
|
|
79
|
-
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 0.0 });
|
|
80
|
-
expect(result?.periodScores['period3']).toEqual({ home: 0.0, away: 1.0 });
|
|
81
|
-
expect(result?.periodScores['period7']).toEqual({ home: 0.0, away: 4.0 });
|
|
82
|
-
expect(result?.periodScores['period8']).toEqual({ home: 2.0, away: 0.0 });
|
|
83
|
-
expect(result?.periodScores['period9']).toEqual({ home: 0.0, away: 0.0 });
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('Should detect completed innings in real live MLB game (6th inning)', () => {
|
|
87
|
-
const result = detectCompletedPeriods(MockMLBLiveSixthInning);
|
|
88
|
-
|
|
89
|
-
expect(result).not.toBeNull();
|
|
90
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]);
|
|
91
|
-
expect(result?.readyForResolution).toBe(true);
|
|
92
|
-
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 1.0 });
|
|
93
|
-
expect(result?.periodScores['period2']).toEqual({ home: 2.0, away: 0.0 });
|
|
94
|
-
expect(result?.periodScores['period3']).toEqual({ home: 1.0, away: 0.0 });
|
|
95
|
-
expect(result?.periodScores['period4']).toEqual({ home: 0.0, away: 0.0 });
|
|
96
|
-
expect(result?.periodScores['period5']).toEqual({ home: 1.0, away: 1.0 });
|
|
97
|
-
expect(result?.currentPeriod).toBe(6);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('Should detect completed periods for real completed NBA game (Warriors vs Suns)', () => {
|
|
101
|
-
const result = detectCompletedPeriods(MockNBACompletedEvent);
|
|
102
|
-
|
|
103
|
-
expect(result).not.toBeNull();
|
|
104
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
105
|
-
expect(result?.readyForResolution).toBe(true);
|
|
106
|
-
expect(result?.periodScores['period1']).toEqual({ home: 33.0, away: 19.0 });
|
|
107
|
-
expect(result?.periodScores['period2']).toEqual({ home: 35.0, away: 30.0 });
|
|
108
|
-
expect(result?.periodScores['period3']).toEqual({ home: 24.0, away: 34.0 });
|
|
109
|
-
expect(result?.periodScores['period4']).toEqual({ home: 26.0, away: 24.0 });
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('Should detect completed quarters at halftime for real NBA game (Warriors vs Suns)', () => {
|
|
113
|
-
const result = detectCompletedPeriods(MockNBALiveAtHalftime);
|
|
114
|
-
|
|
115
|
-
expect(result).not.toBeNull();
|
|
116
|
-
expect(result?.completedPeriods).toEqual([1, 2]); // Both quarters complete at halftime
|
|
117
|
-
expect(result?.readyForResolution).toBe(true);
|
|
118
|
-
expect(result?.periodScores['period1']).toEqual({ home: 33.0, away: 19.0 });
|
|
119
|
-
expect(result?.periodScores['period2']).toEqual({ home: 35.0, away: 30.0 });
|
|
120
|
-
expect(result?.currentPeriod).toBe(2); // Highest period with data at halftime
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('Should return null for real live soccer game with non-numeric period indicator (1H)', () => {
|
|
124
|
-
const result = detectCompletedPeriods(MockSoccerLiveFirstHalfInProgress);
|
|
125
|
-
|
|
126
|
-
// Period 1 exists in data but period is "1H" (non-numeric) meaning first half is still in progress
|
|
127
|
-
// Period 1 is NOT complete until we see period_2 in the data or status becomes completed
|
|
128
|
-
expect(result).toBeNull();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('Should detect completed periods for completed American Football game', () => {
|
|
132
|
-
const event = {
|
|
133
|
-
sport: {
|
|
134
|
-
id: 'football',
|
|
135
|
-
name: 'Football',
|
|
136
|
-
},
|
|
137
|
-
fixture: {
|
|
138
|
-
id: '20250930BEED03AA',
|
|
139
|
-
status: 'completed',
|
|
140
|
-
is_live: false,
|
|
141
|
-
},
|
|
142
|
-
scores: {
|
|
143
|
-
home: {
|
|
144
|
-
total: 28.0,
|
|
145
|
-
periods: {
|
|
146
|
-
period_1: 7.0,
|
|
147
|
-
period_2: 14.0,
|
|
148
|
-
period_3: 0.0,
|
|
149
|
-
period_4: 7.0,
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
away: {
|
|
153
|
-
total: 3.0,
|
|
154
|
-
periods: {
|
|
155
|
-
period_1: 3.0,
|
|
156
|
-
period_2: 0.0,
|
|
157
|
-
period_3: 0.0,
|
|
158
|
-
period_4: 0.0,
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
in_play: {
|
|
163
|
-
period: '4',
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const result = detectCompletedPeriods(event);
|
|
168
|
-
|
|
169
|
-
expect(result).not.toBeNull();
|
|
170
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
171
|
-
expect(result?.readyForResolution).toBe(true);
|
|
172
|
-
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
173
|
-
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 0.0 });
|
|
174
|
-
expect(result?.periodScores['period3']).toEqual({ home: 0.0, away: 0.0 });
|
|
175
|
-
expect(result?.periodScores['period4']).toEqual({ home: 7.0, away: 0.0 });
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('Should detect completed quarters in live American Football game', () => {
|
|
179
|
-
const event = {
|
|
180
|
-
sport: {
|
|
181
|
-
name: 'Football',
|
|
182
|
-
},
|
|
183
|
-
fixture: {
|
|
184
|
-
id: 'nfl-live-123',
|
|
185
|
-
status: 'live',
|
|
186
|
-
is_live: true,
|
|
187
|
-
},
|
|
188
|
-
scores: {
|
|
189
|
-
home: {
|
|
190
|
-
total: 21.0,
|
|
191
|
-
periods: {
|
|
192
|
-
period_1: 7.0,
|
|
193
|
-
period_2: 14.0,
|
|
194
|
-
period_3: 0.0,
|
|
195
|
-
},
|
|
196
|
-
},
|
|
197
|
-
away: {
|
|
198
|
-
total: 10.0,
|
|
199
|
-
periods: {
|
|
200
|
-
period_1: 3.0,
|
|
201
|
-
period_2: 7.0,
|
|
202
|
-
period_3: 0.0,
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
in_play: {
|
|
207
|
-
period: '3',
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const result = detectCompletedPeriods(event);
|
|
212
|
-
|
|
213
|
-
expect(result).not.toBeNull();
|
|
214
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
215
|
-
expect(result?.readyForResolution).toBe(true);
|
|
216
|
-
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
217
|
-
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
218
|
-
expect(result?.currentPeriod).toBe(3);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
it('Should detect all quarters complete in American Football overtime', () => {
|
|
222
|
-
const event = {
|
|
223
|
-
sport: {
|
|
224
|
-
name: 'American Football',
|
|
225
|
-
},
|
|
226
|
-
fixture: {
|
|
227
|
-
id: 'nfl-ot-456',
|
|
228
|
-
status: 'live',
|
|
229
|
-
is_live: true,
|
|
230
|
-
},
|
|
231
|
-
scores: {
|
|
232
|
-
home: {
|
|
233
|
-
total: 24.0,
|
|
234
|
-
periods: {
|
|
235
|
-
period_1: 7.0,
|
|
236
|
-
period_2: 7.0,
|
|
237
|
-
period_3: 3.0,
|
|
238
|
-
period_4: 7.0,
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
away: {
|
|
242
|
-
total: 24.0,
|
|
243
|
-
periods: {
|
|
244
|
-
period_1: 10.0,
|
|
245
|
-
period_2: 7.0,
|
|
246
|
-
period_3: 0.0,
|
|
247
|
-
period_4: 7.0,
|
|
248
|
-
},
|
|
249
|
-
},
|
|
250
|
-
},
|
|
251
|
-
in_play: {
|
|
252
|
-
period: 'overtime',
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
const result = detectCompletedPeriods(event);
|
|
257
|
-
|
|
258
|
-
expect(result).not.toBeNull();
|
|
259
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
260
|
-
expect(result?.readyForResolution).toBe(true);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
it('Should detect completed first half in live soccer game', () => {
|
|
264
|
-
const event = {
|
|
265
|
-
sport: {
|
|
266
|
-
name: 'Soccer',
|
|
267
|
-
},
|
|
268
|
-
fixture: {
|
|
269
|
-
id: 'soccer-123',
|
|
270
|
-
status: 'live',
|
|
271
|
-
is_live: true,
|
|
272
|
-
},
|
|
273
|
-
scores: {
|
|
274
|
-
home: {
|
|
275
|
-
total: 2.0,
|
|
276
|
-
periods: {
|
|
277
|
-
period_1: 1.0,
|
|
278
|
-
},
|
|
279
|
-
},
|
|
280
|
-
away: {
|
|
281
|
-
total: 1.0,
|
|
282
|
-
periods: {
|
|
283
|
-
period_1: 0.0,
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
},
|
|
287
|
-
in_play: {
|
|
288
|
-
period: '2',
|
|
289
|
-
},
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const result = detectCompletedPeriods(event);
|
|
293
|
-
|
|
294
|
-
expect(result).not.toBeNull();
|
|
295
|
-
expect(result?.completedPeriods).toEqual([1]);
|
|
296
|
-
expect(result?.readyForResolution).toBe(true);
|
|
297
|
-
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 0.0 });
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('Should detect completed quarters in live basketball game', () => {
|
|
301
|
-
const event = {
|
|
302
|
-
sport: {
|
|
303
|
-
name: 'Basketball',
|
|
304
|
-
},
|
|
305
|
-
fixture: {
|
|
306
|
-
id: 'nba-789',
|
|
307
|
-
status: 'live',
|
|
308
|
-
is_live: true,
|
|
309
|
-
},
|
|
310
|
-
scores: {
|
|
311
|
-
home: {
|
|
312
|
-
total: 65.0,
|
|
313
|
-
periods: {
|
|
314
|
-
period_1: 25.0,
|
|
315
|
-
period_2: 20.0,
|
|
316
|
-
},
|
|
317
|
-
},
|
|
318
|
-
away: {
|
|
319
|
-
total: 62.0,
|
|
320
|
-
periods: {
|
|
321
|
-
period_1: 22.0,
|
|
322
|
-
period_2: 18.0,
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
in_play: {
|
|
327
|
-
period: '3',
|
|
328
|
-
},
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const result = detectCompletedPeriods(event);
|
|
332
|
-
|
|
333
|
-
expect(result).not.toBeNull();
|
|
334
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
335
|
-
expect(result?.readyForResolution).toBe(true);
|
|
336
|
-
expect(result?.periodScores['period1']).toEqual({ home: 25.0, away: 22.0 });
|
|
337
|
-
expect(result?.periodScores['period2']).toEqual({ home: 20.0, away: 18.0 });
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('Should detect completed sets in live tennis match', () => {
|
|
341
|
-
const event = {
|
|
342
|
-
sport: {
|
|
343
|
-
name: 'Tennis',
|
|
344
|
-
},
|
|
345
|
-
fixture: {
|
|
346
|
-
id: 'tennis-101',
|
|
347
|
-
status: 'live',
|
|
348
|
-
is_live: true,
|
|
349
|
-
},
|
|
350
|
-
scores: {
|
|
351
|
-
home: {
|
|
352
|
-
total: 1.0,
|
|
353
|
-
periods: {
|
|
354
|
-
period_1: 6.0,
|
|
355
|
-
period_2: 3.0,
|
|
356
|
-
},
|
|
357
|
-
},
|
|
358
|
-
away: {
|
|
359
|
-
total: 1.0,
|
|
360
|
-
periods: {
|
|
361
|
-
period_1: 4.0,
|
|
362
|
-
period_2: 6.0,
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
in_play: {
|
|
367
|
-
period: '3',
|
|
368
|
-
},
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
const result = detectCompletedPeriods(event);
|
|
372
|
-
|
|
373
|
-
expect(result).not.toBeNull();
|
|
374
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
375
|
-
expect(result?.readyForResolution).toBe(true);
|
|
376
|
-
expect(result?.periodScores['period1']).toEqual({ home: 6.0, away: 4.0 });
|
|
377
|
-
expect(result?.periodScores['period2']).toEqual({ home: 3.0, away: 6.0 });
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
it('Should detect completed periods in live hockey game', () => {
|
|
381
|
-
const event = {
|
|
382
|
-
sport: {
|
|
383
|
-
name: 'Ice Hockey',
|
|
384
|
-
},
|
|
385
|
-
fixture: {
|
|
386
|
-
id: 'nhl-202',
|
|
387
|
-
status: 'live',
|
|
388
|
-
is_live: true,
|
|
389
|
-
},
|
|
390
|
-
scores: {
|
|
391
|
-
home: {
|
|
392
|
-
total: 2.0,
|
|
393
|
-
periods: {
|
|
394
|
-
period_1: 1.0,
|
|
395
|
-
},
|
|
396
|
-
},
|
|
397
|
-
away: {
|
|
398
|
-
total: 1.0,
|
|
399
|
-
periods: {
|
|
400
|
-
period_1: 1.0,
|
|
401
|
-
},
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
in_play: {
|
|
405
|
-
period: '2',
|
|
406
|
-
},
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
const result = detectCompletedPeriods(event);
|
|
410
|
-
|
|
411
|
-
expect(result).not.toBeNull();
|
|
412
|
-
expect(result?.completedPeriods).toEqual([1]);
|
|
413
|
-
expect(result?.readyForResolution).toBe(true);
|
|
414
|
-
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 1.0 });
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
it('Should detect all periods complete in hockey overtime', () => {
|
|
418
|
-
const event = {
|
|
419
|
-
sport: {
|
|
420
|
-
name: 'Hockey',
|
|
421
|
-
},
|
|
422
|
-
fixture: {
|
|
423
|
-
id: 'nhl-303',
|
|
424
|
-
status: 'live',
|
|
425
|
-
is_live: true,
|
|
426
|
-
},
|
|
427
|
-
scores: {
|
|
428
|
-
home: {
|
|
429
|
-
total: 3.0,
|
|
430
|
-
periods: {
|
|
431
|
-
period_1: 1.0,
|
|
432
|
-
period_2: 1.0,
|
|
433
|
-
period_3: 1.0,
|
|
434
|
-
},
|
|
435
|
-
},
|
|
436
|
-
away: {
|
|
437
|
-
total: 3.0,
|
|
438
|
-
periods: {
|
|
439
|
-
period_1: 0.0,
|
|
440
|
-
period_2: 2.0,
|
|
441
|
-
period_3: 1.0,
|
|
442
|
-
},
|
|
443
|
-
},
|
|
444
|
-
},
|
|
445
|
-
in_play: {
|
|
446
|
-
period: 'overtime',
|
|
447
|
-
},
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
const result = detectCompletedPeriods(event);
|
|
451
|
-
|
|
452
|
-
expect(result).not.toBeNull();
|
|
453
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3]);
|
|
454
|
-
expect(result?.readyForResolution).toBe(true);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
it('Should detect completed innings in live baseball game', () => {
|
|
458
|
-
const event = {
|
|
459
|
-
sport: {
|
|
460
|
-
name: 'Baseball',
|
|
461
|
-
},
|
|
462
|
-
fixture: {
|
|
463
|
-
id: 'mlb-404',
|
|
464
|
-
status: 'live',
|
|
465
|
-
is_live: true,
|
|
466
|
-
},
|
|
467
|
-
scores: {
|
|
468
|
-
home: {
|
|
469
|
-
total: 4.0,
|
|
470
|
-
periods: {
|
|
471
|
-
period_1: 1.0,
|
|
472
|
-
period_2: 0.0,
|
|
473
|
-
period_3: 2.0,
|
|
474
|
-
period_4: 1.0,
|
|
475
|
-
},
|
|
476
|
-
},
|
|
477
|
-
away: {
|
|
478
|
-
total: 3.0,
|
|
479
|
-
periods: {
|
|
480
|
-
period_1: 0.0,
|
|
481
|
-
period_2: 1.0,
|
|
482
|
-
period_3: 1.0,
|
|
483
|
-
period_4: 1.0,
|
|
484
|
-
},
|
|
485
|
-
},
|
|
486
|
-
},
|
|
487
|
-
in_play: {
|
|
488
|
-
period: '5',
|
|
489
|
-
},
|
|
490
|
-
};
|
|
491
|
-
|
|
492
|
-
const result = detectCompletedPeriods(event);
|
|
493
|
-
|
|
494
|
-
expect(result).not.toBeNull();
|
|
495
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
496
|
-
expect(result?.readyForResolution).toBe(true);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
it('Should detect completed sets in live volleyball match', () => {
|
|
500
|
-
const event = {
|
|
501
|
-
sport: {
|
|
502
|
-
name: 'Volleyball',
|
|
503
|
-
},
|
|
504
|
-
fixture: {
|
|
505
|
-
id: 'volleyball-505',
|
|
506
|
-
status: 'live',
|
|
507
|
-
is_live: true,
|
|
508
|
-
},
|
|
509
|
-
scores: {
|
|
510
|
-
home: {
|
|
511
|
-
total: 1.0,
|
|
512
|
-
periods: {
|
|
513
|
-
period_1: 25.0,
|
|
514
|
-
period_2: 22.0,
|
|
515
|
-
},
|
|
516
|
-
},
|
|
517
|
-
away: {
|
|
518
|
-
total: 1.0,
|
|
519
|
-
periods: {
|
|
520
|
-
period_1: 23.0,
|
|
521
|
-
period_2: 25.0,
|
|
522
|
-
},
|
|
523
|
-
},
|
|
524
|
-
},
|
|
525
|
-
in_play: {
|
|
526
|
-
period: '3',
|
|
527
|
-
},
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
const result = detectCompletedPeriods(event);
|
|
531
|
-
|
|
532
|
-
expect(result).not.toBeNull();
|
|
533
|
-
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
534
|
-
expect(result?.readyForResolution).toBe(true);
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
it('Should return null for game not started', () => {
|
|
538
|
-
const event = {
|
|
539
|
-
sport: {
|
|
540
|
-
name: 'Soccer',
|
|
541
|
-
},
|
|
542
|
-
fixture: {
|
|
543
|
-
id: 'future-game',
|
|
544
|
-
status: 'scheduled',
|
|
545
|
-
is_live: false,
|
|
546
|
-
},
|
|
547
|
-
scores: {
|
|
548
|
-
home: {
|
|
549
|
-
total: 0.0,
|
|
550
|
-
periods: {},
|
|
551
|
-
},
|
|
552
|
-
away: {
|
|
553
|
-
total: 0.0,
|
|
554
|
-
periods: {},
|
|
555
|
-
},
|
|
556
|
-
},
|
|
557
|
-
in_play: {
|
|
558
|
-
period: null,
|
|
559
|
-
},
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const result = detectCompletedPeriods(event);
|
|
563
|
-
|
|
564
|
-
expect(result).toBeNull();
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
it('Should return null for game in first period with no completed periods', () => {
|
|
568
|
-
const event = {
|
|
569
|
-
sport: {
|
|
570
|
-
name: 'Basketball',
|
|
571
|
-
},
|
|
572
|
-
fixture: {
|
|
573
|
-
id: 'early-game',
|
|
574
|
-
status: 'live',
|
|
575
|
-
is_live: true,
|
|
576
|
-
},
|
|
577
|
-
scores: {
|
|
578
|
-
home: {
|
|
579
|
-
total: 12.0,
|
|
580
|
-
periods: {},
|
|
581
|
-
},
|
|
582
|
-
away: {
|
|
583
|
-
total: 10.0,
|
|
584
|
-
periods: {},
|
|
585
|
-
},
|
|
586
|
-
},
|
|
587
|
-
in_play: {
|
|
588
|
-
period: '1',
|
|
589
|
-
},
|
|
590
|
-
};
|
|
591
|
-
|
|
592
|
-
const result = detectCompletedPeriods(event);
|
|
593
|
-
|
|
594
|
-
expect(result).toBeNull();
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
it('Bug Fix: Should NOT mark current period as complete when future periods have data (live in period 3)', () => {
|
|
598
|
-
// This tests the bug where OpticOdds API includes future period data with scores (including zeros)
|
|
599
|
-
// Period 3 is currently being played, period 4 has 0-0 scores but hasn't started
|
|
600
|
-
const event = {
|
|
601
|
-
status: 'live',
|
|
602
|
-
is_live: true,
|
|
603
|
-
in_play: { period: '3' },
|
|
604
|
-
scores: {
|
|
605
|
-
home: { periods: { period_1: 33, period_2: 32, period_3: 12, period_4: 0 } },
|
|
606
|
-
away: { periods: { period_1: 23, period_2: 27, period_3: 18, period_4: 0 } },
|
|
607
|
-
},
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
const result = detectCompletedPeriods(event);
|
|
611
|
-
|
|
612
|
-
expect(result).not.toBeNull();
|
|
613
|
-
expect(result?.completedPeriods).toEqual([1, 2]); // Only periods 1 and 2 are complete
|
|
614
|
-
expect(result?.currentPeriod).toBe(3);
|
|
615
|
-
// Period 3 is currently being played, so NOT complete
|
|
616
|
-
// Period 4 has data (0-0) but hasn't started, so NOT complete
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
it('Bug Fix: Should mark all periods as complete for completed game', () => {
|
|
620
|
-
const event = {
|
|
621
|
-
status: 'completed',
|
|
622
|
-
is_live: false,
|
|
623
|
-
scores: {
|
|
624
|
-
home: { periods: { period_1: 33, period_2: 32, period_3: 25, period_4: 20 } },
|
|
625
|
-
away: { periods: { period_1: 23, period_2: 27, period_3: 18, period_4: 22 } },
|
|
626
|
-
},
|
|
627
|
-
};
|
|
628
|
-
|
|
629
|
-
const result = detectCompletedPeriods(event);
|
|
630
|
-
|
|
631
|
-
expect(result).not.toBeNull();
|
|
632
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
633
|
-
// All periods complete because game status is 'completed'
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
it('Bug Fix: Should NOT mark current period as complete during transition to period 4', () => {
|
|
637
|
-
const event = {
|
|
638
|
-
status: 'live',
|
|
639
|
-
is_live: true,
|
|
640
|
-
in_play: { period: '4' },
|
|
641
|
-
scores: {
|
|
642
|
-
home: { periods: { period_1: 33, period_2: 32, period_3: 25, period_4: 5 } },
|
|
643
|
-
away: { periods: { period_1: 23, period_2: 27, period_3: 18, period_4: 3 } },
|
|
644
|
-
},
|
|
645
|
-
};
|
|
646
|
-
|
|
647
|
-
const result = detectCompletedPeriods(event);
|
|
648
|
-
|
|
649
|
-
expect(result).not.toBeNull();
|
|
650
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3]); // Periods 1, 2, 3 are complete
|
|
651
|
-
expect(result?.currentPeriod).toBe(4);
|
|
652
|
-
// Period 4 is currently in_play, so NOT complete yet
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
it('Basketball at halftime should mark periods 1 AND 2 as complete (quarters-based)', () => {
|
|
656
|
-
const event = {
|
|
657
|
-
sport: {
|
|
658
|
-
id: 'basketball',
|
|
659
|
-
name: 'Basketball',
|
|
660
|
-
},
|
|
661
|
-
fixture: {
|
|
662
|
-
id: 'nba-halftime-123',
|
|
663
|
-
status: 'half',
|
|
664
|
-
is_live: true,
|
|
665
|
-
},
|
|
666
|
-
scores: {
|
|
667
|
-
home: {
|
|
668
|
-
total: 52.0,
|
|
669
|
-
periods: {
|
|
670
|
-
period_1: 25.0,
|
|
671
|
-
period_2: 27.0,
|
|
672
|
-
},
|
|
673
|
-
},
|
|
674
|
-
away: {
|
|
675
|
-
total: 48.0,
|
|
676
|
-
periods: {
|
|
677
|
-
period_1: 22.0,
|
|
678
|
-
period_2: 26.0,
|
|
679
|
-
},
|
|
680
|
-
},
|
|
681
|
-
},
|
|
682
|
-
in_play: {
|
|
683
|
-
period: 'half',
|
|
684
|
-
},
|
|
685
|
-
};
|
|
686
|
-
|
|
687
|
-
const result = detectCompletedPeriods(event);
|
|
688
|
-
|
|
689
|
-
expect(result).not.toBeNull();
|
|
690
|
-
expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
|
|
691
|
-
expect(result?.readyForResolution).toBe(true);
|
|
692
|
-
expect(result?.periodScores['period1']).toEqual({ home: 25.0, away: 22.0 });
|
|
693
|
-
expect(result?.periodScores['period2']).toEqual({ home: 27.0, away: 26.0 });
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
it('Basketball at halftime with status "halftime" should mark periods 1 AND 2 as complete', () => {
|
|
697
|
-
const event = {
|
|
698
|
-
sport: {
|
|
699
|
-
id: 'basketball',
|
|
700
|
-
name: 'Basketball',
|
|
701
|
-
},
|
|
702
|
-
fixture: {
|
|
703
|
-
id: 'nba-halftime-456',
|
|
704
|
-
status: 'halftime',
|
|
705
|
-
is_live: true,
|
|
706
|
-
},
|
|
707
|
-
scores: {
|
|
708
|
-
home: {
|
|
709
|
-
total: 55.0,
|
|
710
|
-
periods: {
|
|
711
|
-
period_1: 28.0,
|
|
712
|
-
period_2: 27.0,
|
|
713
|
-
},
|
|
714
|
-
},
|
|
715
|
-
away: {
|
|
716
|
-
total: 50.0,
|
|
717
|
-
periods: {
|
|
718
|
-
period_1: 24.0,
|
|
719
|
-
period_2: 26.0,
|
|
720
|
-
},
|
|
721
|
-
},
|
|
722
|
-
},
|
|
723
|
-
in_play: {
|
|
724
|
-
period: '2',
|
|
725
|
-
},
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
const result = detectCompletedPeriods(event);
|
|
729
|
-
|
|
730
|
-
expect(result).not.toBeNull();
|
|
731
|
-
expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
|
|
732
|
-
expect(result?.readyForResolution).toBe(true);
|
|
733
|
-
});
|
|
734
|
-
|
|
735
|
-
it('Football at halftime should mark periods 1 AND 2 as complete (quarters-based)', () => {
|
|
736
|
-
const event = {
|
|
737
|
-
sport: {
|
|
738
|
-
id: 'football',
|
|
739
|
-
name: 'Football',
|
|
740
|
-
},
|
|
741
|
-
fixture: {
|
|
742
|
-
id: 'nfl-halftime-789',
|
|
743
|
-
status: 'half',
|
|
744
|
-
is_live: true,
|
|
745
|
-
},
|
|
746
|
-
scores: {
|
|
747
|
-
home: {
|
|
748
|
-
total: 21.0,
|
|
749
|
-
periods: {
|
|
750
|
-
period_1: 7.0,
|
|
751
|
-
period_2: 14.0,
|
|
752
|
-
},
|
|
753
|
-
},
|
|
754
|
-
away: {
|
|
755
|
-
total: 10.0,
|
|
756
|
-
periods: {
|
|
757
|
-
period_1: 3.0,
|
|
758
|
-
period_2: 7.0,
|
|
759
|
-
},
|
|
760
|
-
},
|
|
761
|
-
},
|
|
762
|
-
in_play: {
|
|
763
|
-
period: 'half',
|
|
764
|
-
},
|
|
765
|
-
};
|
|
766
|
-
|
|
767
|
-
const result = detectCompletedPeriods(event);
|
|
768
|
-
|
|
769
|
-
expect(result).not.toBeNull();
|
|
770
|
-
expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
|
|
771
|
-
expect(result?.readyForResolution).toBe(true);
|
|
772
|
-
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
773
|
-
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
it('Football at halftime with status "halftime" should mark periods 1 AND 2 as complete', () => {
|
|
777
|
-
const event = {
|
|
778
|
-
sport: {
|
|
779
|
-
id: 'football',
|
|
780
|
-
name: 'American Football',
|
|
781
|
-
},
|
|
782
|
-
fixture: {
|
|
783
|
-
id: 'nfl-halftime-890',
|
|
784
|
-
status: 'halftime',
|
|
785
|
-
is_live: true,
|
|
786
|
-
},
|
|
787
|
-
scores: {
|
|
788
|
-
home: {
|
|
789
|
-
total: 17.0,
|
|
790
|
-
periods: {
|
|
791
|
-
period_1: 10.0,
|
|
792
|
-
period_2: 7.0,
|
|
793
|
-
},
|
|
794
|
-
},
|
|
795
|
-
away: {
|
|
796
|
-
total: 14.0,
|
|
797
|
-
periods: {
|
|
798
|
-
period_1: 7.0,
|
|
799
|
-
period_2: 7.0,
|
|
800
|
-
},
|
|
801
|
-
},
|
|
802
|
-
},
|
|
803
|
-
in_play: {
|
|
804
|
-
period: '2',
|
|
805
|
-
},
|
|
806
|
-
};
|
|
807
|
-
|
|
808
|
-
const result = detectCompletedPeriods(event);
|
|
809
|
-
|
|
810
|
-
expect(result).not.toBeNull();
|
|
811
|
-
expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
|
|
812
|
-
expect(result?.readyForResolution).toBe(true);
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
it('Baseball at halftime should mark periods 1-5 as complete (innings-based)', () => {
|
|
816
|
-
const event = {
|
|
817
|
-
sport: {
|
|
818
|
-
id: 'baseball',
|
|
819
|
-
name: 'Baseball',
|
|
820
|
-
},
|
|
821
|
-
fixture: {
|
|
822
|
-
id: 'mlb-halftime-789',
|
|
823
|
-
status: 'half',
|
|
824
|
-
is_live: true,
|
|
825
|
-
},
|
|
826
|
-
scores: {
|
|
827
|
-
home: {
|
|
828
|
-
total: 3.0,
|
|
829
|
-
periods: {
|
|
830
|
-
period_1: 0.0,
|
|
831
|
-
period_2: 1.0,
|
|
832
|
-
period_3: 1.0,
|
|
833
|
-
period_4: 0.0,
|
|
834
|
-
period_5: 1.0,
|
|
835
|
-
},
|
|
836
|
-
},
|
|
837
|
-
away: {
|
|
838
|
-
total: 2.0,
|
|
839
|
-
periods: {
|
|
840
|
-
period_1: 1.0,
|
|
841
|
-
period_2: 0.0,
|
|
842
|
-
period_3: 0.0,
|
|
843
|
-
period_4: 1.0,
|
|
844
|
-
period_5: 0.0,
|
|
845
|
-
},
|
|
846
|
-
},
|
|
847
|
-
},
|
|
848
|
-
in_play: {
|
|
849
|
-
period: 'half',
|
|
850
|
-
},
|
|
851
|
-
};
|
|
852
|
-
|
|
853
|
-
const result = detectCompletedPeriods(event);
|
|
854
|
-
|
|
855
|
-
expect(result).not.toBeNull();
|
|
856
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]); // First 5 innings complete at halftime
|
|
857
|
-
expect(result?.readyForResolution).toBe(true);
|
|
858
|
-
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 1.0 });
|
|
859
|
-
expect(result?.periodScores['period5']).toEqual({ home: 1.0, away: 0.0 });
|
|
860
|
-
});
|
|
861
|
-
|
|
862
|
-
it('Soccer at halftime should mark only period 1 as complete (halves-based)', () => {
|
|
863
|
-
const event = {
|
|
864
|
-
sport: {
|
|
865
|
-
id: 'soccer',
|
|
866
|
-
name: 'Soccer',
|
|
867
|
-
},
|
|
868
|
-
fixture: {
|
|
869
|
-
id: 'soccer-halftime-101',
|
|
870
|
-
status: 'halftime',
|
|
871
|
-
is_live: true,
|
|
872
|
-
},
|
|
873
|
-
scores: {
|
|
874
|
-
home: {
|
|
875
|
-
total: 2.0,
|
|
876
|
-
periods: {
|
|
877
|
-
period_1: 2.0,
|
|
878
|
-
},
|
|
879
|
-
},
|
|
880
|
-
away: {
|
|
881
|
-
total: 1.0,
|
|
882
|
-
periods: {
|
|
883
|
-
period_1: 1.0,
|
|
884
|
-
},
|
|
885
|
-
},
|
|
886
|
-
},
|
|
887
|
-
in_play: {
|
|
888
|
-
period: 'half',
|
|
889
|
-
},
|
|
890
|
-
};
|
|
891
|
-
|
|
892
|
-
const result = detectCompletedPeriods(event);
|
|
893
|
-
|
|
894
|
-
expect(result).not.toBeNull();
|
|
895
|
-
expect(result?.completedPeriods).toEqual([1]); // Only first half complete at halftime
|
|
896
|
-
expect(result?.readyForResolution).toBe(true);
|
|
897
|
-
expect(result?.periodScores['period1']).toEqual({ home: 2.0, away: 1.0 });
|
|
898
|
-
});
|
|
899
|
-
|
|
900
|
-
it('Hockey at halftime should NOT mark any periods as complete (period-based, no halftime concept)', () => {
|
|
901
|
-
const event = {
|
|
902
|
-
sport: {
|
|
903
|
-
id: 'hockey',
|
|
904
|
-
name: 'Hockey',
|
|
905
|
-
},
|
|
906
|
-
fixture: {
|
|
907
|
-
id: 'nhl-halftime-202',
|
|
908
|
-
status: 'half',
|
|
909
|
-
is_live: true,
|
|
910
|
-
},
|
|
911
|
-
scores: {
|
|
912
|
-
home: {
|
|
913
|
-
total: 2.0,
|
|
914
|
-
periods: {
|
|
915
|
-
period_1: 1.0,
|
|
916
|
-
period_2: 1.0,
|
|
917
|
-
},
|
|
918
|
-
},
|
|
919
|
-
away: {
|
|
920
|
-
total: 1.0,
|
|
921
|
-
periods: {
|
|
922
|
-
period_1: 0.0,
|
|
923
|
-
period_2: 1.0,
|
|
924
|
-
},
|
|
925
|
-
},
|
|
926
|
-
},
|
|
927
|
-
in_play: {
|
|
928
|
-
period: 'half',
|
|
929
|
-
},
|
|
930
|
-
};
|
|
931
|
-
|
|
932
|
-
const result = detectCompletedPeriods(event);
|
|
933
|
-
|
|
934
|
-
// Hockey doesn't have traditional halftime, so halftime status shouldn't mark periods complete
|
|
935
|
-
// Periods should only be marked complete based on in_play.period progression
|
|
936
|
-
expect(result).toBeNull();
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
it('Unknown sport at halftime should default to PERIOD_BASED (no halftime processing)', () => {
|
|
940
|
-
const event = {
|
|
941
|
-
sport: {
|
|
942
|
-
id: 'unknown_sport',
|
|
943
|
-
name: 'Unknown Sport',
|
|
944
|
-
},
|
|
945
|
-
fixture: {
|
|
946
|
-
id: 'unknown-halftime-123',
|
|
947
|
-
status: 'half',
|
|
948
|
-
is_live: true,
|
|
949
|
-
},
|
|
950
|
-
scores: {
|
|
951
|
-
home: {
|
|
952
|
-
total: 50.0,
|
|
953
|
-
periods: {
|
|
954
|
-
period_1: 25.0,
|
|
955
|
-
period_2: 25.0,
|
|
956
|
-
},
|
|
957
|
-
},
|
|
958
|
-
away: {
|
|
959
|
-
total: 40.0,
|
|
960
|
-
periods: {
|
|
961
|
-
period_1: 20.0,
|
|
962
|
-
period_2: 20.0,
|
|
963
|
-
},
|
|
964
|
-
},
|
|
965
|
-
},
|
|
966
|
-
in_play: {
|
|
967
|
-
period: 'half',
|
|
968
|
-
},
|
|
969
|
-
};
|
|
970
|
-
|
|
971
|
-
const result = detectCompletedPeriods(event);
|
|
972
|
-
|
|
973
|
-
// Unknown sports default to PERIOD_BASED (no halftime processing)
|
|
974
|
-
// So halftime status should NOT mark any periods as complete
|
|
975
|
-
expect(result).toBeNull();
|
|
976
|
-
});
|
|
977
|
-
|
|
978
|
-
it('Event with missing sport.id should default to PERIOD_BASED (no halftime processing)', () => {
|
|
979
|
-
const event = {
|
|
980
|
-
sport: {
|
|
981
|
-
name: 'Some Sport',
|
|
982
|
-
},
|
|
983
|
-
fixture: {
|
|
984
|
-
id: 'no-sport-id-123',
|
|
985
|
-
status: 'half',
|
|
986
|
-
is_live: true,
|
|
987
|
-
},
|
|
988
|
-
scores: {
|
|
989
|
-
home: {
|
|
990
|
-
total: 50.0,
|
|
991
|
-
periods: {
|
|
992
|
-
period_1: 25.0,
|
|
993
|
-
period_2: 25.0,
|
|
994
|
-
},
|
|
995
|
-
},
|
|
996
|
-
away: {
|
|
997
|
-
total: 40.0,
|
|
998
|
-
periods: {
|
|
999
|
-
period_1: 20.0,
|
|
1000
|
-
period_2: 20.0,
|
|
1001
|
-
},
|
|
1002
|
-
},
|
|
1003
|
-
},
|
|
1004
|
-
in_play: {
|
|
1005
|
-
period: 'half',
|
|
1006
|
-
},
|
|
1007
|
-
};
|
|
1008
|
-
|
|
1009
|
-
const result = detectCompletedPeriods(event);
|
|
1010
|
-
|
|
1011
|
-
// Missing sport.id defaults to PERIOD_BASED (no halftime processing)
|
|
1012
|
-
expect(result).toBeNull();
|
|
1013
|
-
});
|
|
1014
|
-
});
|
|
1015
|
-
|
|
1016
|
-
describe('canResolveMarketsForEvent', () => {
|
|
1017
|
-
describe('Single typeId checks', () => {
|
|
1018
|
-
it('Should return true for 1st period typeId when period 1 is complete (Soccer 2nd half)', () => {
|
|
1019
|
-
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10021, SportPeriodType.HALVES_BASED);
|
|
1020
|
-
expect(result).toBe(true);
|
|
1021
|
-
});
|
|
1022
|
-
|
|
1023
|
-
it('Should return false for 2nd period typeId when only period 1 is complete', () => {
|
|
1024
|
-
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10022, SportPeriodType.HALVES_BASED);
|
|
1025
|
-
expect(result).toBe(false);
|
|
1026
|
-
});
|
|
1027
|
-
|
|
1028
|
-
it('Should return false for full game typeId during live game', () => {
|
|
1029
|
-
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10001, SportPeriodType.HALVES_BASED);
|
|
1030
|
-
expect(result).toBe(false);
|
|
1031
|
-
});
|
|
1032
|
-
|
|
1033
|
-
it('Should return true for full game typeId when game is completed', () => {
|
|
1034
|
-
const result = canResolveMarketsForEvent(MockSoccerCompletedEvent, 10001, SportPeriodType.HALVES_BASED);
|
|
1035
|
-
expect(result).toBe(true);
|
|
1036
|
-
});
|
|
1037
|
-
|
|
1038
|
-
it('Should return true for 1st quarter typeId when quarter 1 complete (NFL)', () => {
|
|
1039
|
-
const result = canResolveMarketsForEvent(
|
|
1040
|
-
MockNFLLiveThirdQuarter,
|
|
1041
|
-
10021,
|
|
1042
|
-
SportPeriodType.QUARTERS_BASED
|
|
1043
|
-
);
|
|
1044
|
-
expect(result).toBe(true);
|
|
1045
|
-
});
|
|
1046
|
-
|
|
1047
|
-
it('Should return true for 2nd quarter typeId when quarters 1-2 complete (NFL)', () => {
|
|
1048
|
-
const result = canResolveMarketsForEvent(
|
|
1049
|
-
MockNFLLiveThirdQuarter,
|
|
1050
|
-
10022,
|
|
1051
|
-
SportPeriodType.QUARTERS_BASED
|
|
1052
|
-
);
|
|
1053
|
-
expect(result).toBe(true);
|
|
1054
|
-
});
|
|
1055
|
-
|
|
1056
|
-
it('Should return false for 3rd quarter typeId during 3rd quarter (NFL)', () => {
|
|
1057
|
-
const result = canResolveMarketsForEvent(
|
|
1058
|
-
MockNFLLiveThirdQuarter,
|
|
1059
|
-
10023,
|
|
1060
|
-
SportPeriodType.QUARTERS_BASED
|
|
1061
|
-
);
|
|
1062
|
-
expect(result).toBe(false);
|
|
1063
|
-
});
|
|
1064
|
-
|
|
1065
|
-
it('Should return true for all quarter typeIds when game is completed (NFL)', () => {
|
|
1066
|
-
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10021, SportPeriodType.QUARTERS_BASED)).toBe(
|
|
1067
|
-
true
|
|
1068
|
-
);
|
|
1069
|
-
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10022, SportPeriodType.QUARTERS_BASED)).toBe(
|
|
1070
|
-
true
|
|
1071
|
-
);
|
|
1072
|
-
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10023, SportPeriodType.QUARTERS_BASED)).toBe(
|
|
1073
|
-
true
|
|
1074
|
-
);
|
|
1075
|
-
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10024, SportPeriodType.QUARTERS_BASED)).toBe(
|
|
1076
|
-
true
|
|
1077
|
-
);
|
|
1078
|
-
});
|
|
1079
|
-
|
|
1080
|
-
it('Should return false when no periods are complete', () => {
|
|
1081
|
-
const result = canResolveMarketsForEvent(MockSoccerLiveFirstHalf, 10021, SportPeriodType.HALVES_BASED);
|
|
1082
|
-
expect(result).toBe(false);
|
|
1083
|
-
});
|
|
1084
|
-
|
|
1085
|
-
it('Should return false for non-existent typeId', () => {
|
|
1086
|
-
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 99999, SportPeriodType.HALVES_BASED);
|
|
1087
|
-
expect(result).toBe(false);
|
|
1088
|
-
});
|
|
1089
|
-
});
|
|
1090
|
-
|
|
1091
|
-
describe('Batch typeIds checks', () => {
|
|
1092
|
-
it('Should return only resolvable typeIds for live soccer in 2nd half', () => {
|
|
1093
|
-
const typeIds = [10021, 10022, 10031, 10001];
|
|
1094
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1095
|
-
MockSoccerLiveSecondHalf,
|
|
1096
|
-
typeIds,
|
|
1097
|
-
SportPeriodType.HALVES_BASED
|
|
1098
|
-
);
|
|
1099
|
-
|
|
1100
|
-
expect(result).toEqual([10021, 10031]); // Only period 1 typeIds
|
|
1101
|
-
});
|
|
1102
|
-
|
|
1103
|
-
it('Should exclude full game typeIds during live game', () => {
|
|
1104
|
-
const typeIds = [10021, 10001, 10002, 10003];
|
|
1105
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1106
|
-
MockNFLLiveThirdQuarter,
|
|
1107
|
-
typeIds,
|
|
1108
|
-
SportPeriodType.QUARTERS_BASED
|
|
1109
|
-
);
|
|
1110
|
-
|
|
1111
|
-
expect(result).toEqual([10021]); // Full game typeIds excluded
|
|
1112
|
-
});
|
|
1113
|
-
|
|
1114
|
-
it('Should include full game typeIds when game is completed', () => {
|
|
1115
|
-
const typeIds = [10021, 10022, 10001, 10002];
|
|
1116
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1117
|
-
MockSoccerCompletedEvent,
|
|
1118
|
-
typeIds,
|
|
1119
|
-
SportPeriodType.HALVES_BASED
|
|
1120
|
-
);
|
|
1121
|
-
|
|
1122
|
-
expect(result).toEqual([10021, 10022, 10001, 10002]);
|
|
1123
|
-
});
|
|
1124
|
-
|
|
1125
|
-
it('Should return empty array when no typeIds are resolvable', () => {
|
|
1126
|
-
const typeIds = [10022, 10023, 10024];
|
|
1127
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1128
|
-
MockSoccerLiveSecondHalf,
|
|
1129
|
-
typeIds,
|
|
1130
|
-
SportPeriodType.HALVES_BASED
|
|
1131
|
-
);
|
|
1132
|
-
|
|
1133
|
-
expect(result).toEqual([]);
|
|
1134
|
-
});
|
|
1135
|
-
|
|
1136
|
-
it('Should return multiple period typeIds for NFL game in 3rd quarter', () => {
|
|
1137
|
-
const typeIds = [10021, 10022, 10023, 10024, 10031, 10032, 10051];
|
|
1138
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1139
|
-
MockNFLLiveThirdQuarter,
|
|
1140
|
-
typeIds,
|
|
1141
|
-
SportPeriodType.QUARTERS_BASED
|
|
1142
|
-
);
|
|
1143
|
-
|
|
1144
|
-
// Periods 1 and 2 are complete (period 2 also completes 1st half = 10051)
|
|
1145
|
-
expect(result).toEqual([10021, 10022, 10031, 10032, 10051]);
|
|
1146
|
-
});
|
|
1147
|
-
|
|
1148
|
-
it('Should handle all 9 periods for completed MLB game', () => {
|
|
1149
|
-
const typeIds = [10021, 10022, 10023, 10024, 10025, 10026, 10027, 10028, 10029];
|
|
1150
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1151
|
-
MockMLBCompletedEvent,
|
|
1152
|
-
typeIds,
|
|
1153
|
-
SportPeriodType.INNINGS_BASED
|
|
1154
|
-
);
|
|
1155
|
-
|
|
1156
|
-
expect(result).toEqual(typeIds); // All 9 innings complete
|
|
1157
|
-
});
|
|
1158
|
-
|
|
1159
|
-
it('Should return empty array when no periods complete', () => {
|
|
1160
|
-
const typeIds = [10021, 10022, 10031];
|
|
1161
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1162
|
-
MockSoccerLiveFirstHalf,
|
|
1163
|
-
typeIds,
|
|
1164
|
-
SportPeriodType.HALVES_BASED
|
|
1165
|
-
);
|
|
1166
|
-
|
|
1167
|
-
expect(result).toEqual([]);
|
|
1168
|
-
});
|
|
1169
|
-
});
|
|
1170
|
-
|
|
1171
|
-
describe('Multiple typeIds boolean array checks', () => {
|
|
1172
|
-
it('Should return boolean array for live soccer in 2nd half', () => {
|
|
1173
|
-
const typeIds = [10021, 10022, 10031, 10001];
|
|
1174
|
-
const result = canResolveMultipleTypeIdsForEvent(
|
|
1175
|
-
MockSoccerLiveSecondHalf,
|
|
1176
|
-
typeIds,
|
|
1177
|
-
SportPeriodType.HALVES_BASED
|
|
1178
|
-
);
|
|
1179
|
-
|
|
1180
|
-
expect(result).toEqual([true, false, true, false]); // Period 1 typeIds are true, period 2 and full game are false
|
|
1181
|
-
});
|
|
1182
|
-
|
|
1183
|
-
it('Should return false for full game typeIds during live game', () => {
|
|
1184
|
-
const typeIds = [10021, 10001, 10002, 10003];
|
|
1185
|
-
const result = canResolveMultipleTypeIdsForEvent(
|
|
1186
|
-
MockNFLLiveThirdQuarter,
|
|
1187
|
-
typeIds,
|
|
1188
|
-
SportPeriodType.QUARTERS_BASED
|
|
1189
|
-
);
|
|
1190
|
-
|
|
1191
|
-
expect(result).toEqual([true, false, false, false]); // Only period 1 is true
|
|
1192
|
-
});
|
|
1193
|
-
|
|
1194
|
-
it('Should return all true for completed game', () => {
|
|
1195
|
-
const typeIds = [10021, 10022, 10001, 10002];
|
|
1196
|
-
const result = canResolveMultipleTypeIdsForEvent(
|
|
1197
|
-
MockSoccerCompletedEvent,
|
|
1198
|
-
typeIds,
|
|
1199
|
-
SportPeriodType.HALVES_BASED
|
|
1200
|
-
);
|
|
1201
|
-
|
|
1202
|
-
expect(result).toEqual([true, true, true, true]); // All complete
|
|
1203
|
-
});
|
|
1204
|
-
|
|
1205
|
-
it('Should return all false when no typeIds are resolvable', () => {
|
|
1206
|
-
const typeIds = [10022, 10023, 10024];
|
|
1207
|
-
const result = canResolveMultipleTypeIdsForEvent(
|
|
1208
|
-
MockSoccerLiveSecondHalf,
|
|
1209
|
-
typeIds,
|
|
1210
|
-
SportPeriodType.HALVES_BASED
|
|
1211
|
-
);
|
|
1212
|
-
|
|
1213
|
-
expect(result).toEqual([false, false, false]);
|
|
1214
|
-
});
|
|
1215
|
-
|
|
1216
|
-
it('Should return mixed booleans for NFL game in 3rd quarter', () => {
|
|
1217
|
-
const typeIds = [10021, 10022, 10023, 10024, 10031, 10032, 10051];
|
|
1218
|
-
const result = canResolveMultipleTypeIdsForEvent(
|
|
1219
|
-
MockNFLLiveThirdQuarter,
|
|
1220
|
-
typeIds,
|
|
1221
|
-
SportPeriodType.QUARTERS_BASED
|
|
1222
|
-
);
|
|
1223
|
-
|
|
1224
|
-
// Periods 1 and 2 are complete (period 2 also completes 1st half = 10051)
|
|
1225
|
-
expect(result).toEqual([true, true, false, false, true, true, true]);
|
|
1226
|
-
});
|
|
1227
|
-
|
|
1228
|
-
it('Should handle all 9 periods for completed MLB game', () => {
|
|
1229
|
-
const typeIds = [10021, 10022, 10023, 10024, 10025, 10026, 10027, 10028, 10029];
|
|
1230
|
-
const result = canResolveMultipleTypeIdsForEvent(
|
|
1231
|
-
MockMLBCompletedEvent,
|
|
1232
|
-
typeIds,
|
|
1233
|
-
SportPeriodType.INNINGS_BASED
|
|
1234
|
-
);
|
|
1235
|
-
|
|
1236
|
-
expect(result).toEqual([true, true, true, true, true, true, true, true, true]); // All 9 innings complete
|
|
1237
|
-
});
|
|
1238
|
-
|
|
1239
|
-
it('Should return all false when no periods complete', () => {
|
|
1240
|
-
const typeIds = [10021, 10022, 10031];
|
|
1241
|
-
const result = canResolveMultipleTypeIdsForEvent(
|
|
1242
|
-
MockSoccerLiveFirstHalf,
|
|
1243
|
-
typeIds,
|
|
1244
|
-
SportPeriodType.HALVES_BASED
|
|
1245
|
-
);
|
|
1246
|
-
|
|
1247
|
-
expect(result).toEqual([false, false, false]);
|
|
1248
|
-
});
|
|
1249
|
-
|
|
1250
|
-
it('Should work with numeric sport type parameter', () => {
|
|
1251
|
-
const typeIds = [10021, 10022];
|
|
1252
|
-
const result = canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, 0); // 0 = HALVES_BASED
|
|
1253
|
-
|
|
1254
|
-
expect(result).toEqual([true, false]);
|
|
1255
|
-
});
|
|
1256
|
-
});
|
|
1257
|
-
|
|
1258
|
-
describe('Edge cases', () => {
|
|
1259
|
-
it('Should handle event with no completed periods', () => {
|
|
1260
|
-
const result = canResolveMarketsForEvent(
|
|
1261
|
-
MockSoccerLiveFirstHalfInProgress,
|
|
1262
|
-
10021,
|
|
1263
|
-
SportPeriodType.HALVES_BASED
|
|
1264
|
-
);
|
|
1265
|
-
expect(result).toBe(false);
|
|
1266
|
-
});
|
|
1267
|
-
|
|
1268
|
-
it('Should respect full game typeIds list', () => {
|
|
1269
|
-
// Test all full game typeIds
|
|
1270
|
-
const fullGameTypeIds = [0, 10001, 10002, 10003, 10004, 10010, 10011, 10012];
|
|
1271
|
-
|
|
1272
|
-
fullGameTypeIds.forEach((typeId) => {
|
|
1273
|
-
const result = canResolveMarketsForEvent(
|
|
1274
|
-
MockNFLLiveThirdQuarter,
|
|
1275
|
-
typeId,
|
|
1276
|
-
SportPeriodType.QUARTERS_BASED
|
|
1277
|
-
);
|
|
1278
|
-
expect(result).toBe(false);
|
|
1279
|
-
});
|
|
1280
|
-
});
|
|
1281
|
-
|
|
1282
|
-
it('Should work with sport type parameter for single typeId', () => {
|
|
1283
|
-
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10021, SportPeriodType.HALVES_BASED);
|
|
1284
|
-
expect(result).toBe(true);
|
|
1285
|
-
});
|
|
1286
|
-
|
|
1287
|
-
it('Should work with sport type parameter for batch typeIds', () => {
|
|
1288
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1289
|
-
MockSoccerLiveSecondHalf,
|
|
1290
|
-
[10021, 10022],
|
|
1291
|
-
SportPeriodType.HALVES_BASED
|
|
1292
|
-
);
|
|
1293
|
-
expect(result).toEqual([10021]);
|
|
1294
|
-
});
|
|
1295
|
-
});
|
|
1296
|
-
|
|
1297
|
-
describe('Real overtime NFL game (Rams vs 49ers)', () => {
|
|
1298
|
-
it('Should detect all 5 periods complete including overtime', () => {
|
|
1299
|
-
const result = detectCompletedPeriods(MockNFLCompletedWithOvertime);
|
|
1300
|
-
|
|
1301
|
-
expect(result).not.toBeNull();
|
|
1302
|
-
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]);
|
|
1303
|
-
expect(result?.readyForResolution).toBe(true);
|
|
1304
|
-
expect(result?.periodScores['period5']).toEqual({ home: 0.0, away: 3.0 });
|
|
1305
|
-
});
|
|
1306
|
-
|
|
1307
|
-
it('Should resolve all quarter typeIds (10021-10024) for completed overtime game', () => {
|
|
1308
|
-
expect(
|
|
1309
|
-
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10021, SportPeriodType.QUARTERS_BASED)
|
|
1310
|
-
).toBe(true);
|
|
1311
|
-
expect(
|
|
1312
|
-
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10022, SportPeriodType.QUARTERS_BASED)
|
|
1313
|
-
).toBe(true);
|
|
1314
|
-
expect(
|
|
1315
|
-
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10023, SportPeriodType.QUARTERS_BASED)
|
|
1316
|
-
).toBe(true);
|
|
1317
|
-
expect(
|
|
1318
|
-
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10024, SportPeriodType.QUARTERS_BASED)
|
|
1319
|
-
).toBe(true);
|
|
1320
|
-
});
|
|
1321
|
-
|
|
1322
|
-
it('Should NOT resolve overtime period typeId (10025) - overtime has no specific markets', () => {
|
|
1323
|
-
const result = canResolveMarketsForEvent(
|
|
1324
|
-
MockNFLCompletedWithOvertime,
|
|
1325
|
-
10025,
|
|
1326
|
-
SportPeriodType.QUARTERS_BASED
|
|
1327
|
-
);
|
|
1328
|
-
expect(result).toBe(false);
|
|
1329
|
-
});
|
|
1330
|
-
|
|
1331
|
-
it('Should resolve full game typeIds for completed overtime game', () => {
|
|
1332
|
-
expect(
|
|
1333
|
-
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10001, SportPeriodType.QUARTERS_BASED)
|
|
1334
|
-
).toBe(true);
|
|
1335
|
-
expect(
|
|
1336
|
-
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10002, SportPeriodType.QUARTERS_BASED)
|
|
1337
|
-
).toBe(true);
|
|
1338
|
-
});
|
|
1339
|
-
|
|
1340
|
-
it('Should return all resolvable typeIds excluding overtime in batch check', () => {
|
|
1341
|
-
const typeIds = [10021, 10022, 10023, 10024, 10025, 10001];
|
|
1342
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1343
|
-
MockNFLCompletedWithOvertime,
|
|
1344
|
-
typeIds,
|
|
1345
|
-
SportPeriodType.QUARTERS_BASED
|
|
1346
|
-
);
|
|
1347
|
-
|
|
1348
|
-
// 10025 (overtime) should not be included as period 5 has no specific markets
|
|
1349
|
-
expect(result).toEqual([10021, 10022, 10023, 10024, 10001]);
|
|
1350
|
-
});
|
|
1351
|
-
|
|
1352
|
-
it('Should return false for 8th period typeId (period did not occur)', () => {
|
|
1353
|
-
const result = canResolveMarketsForEvent(
|
|
1354
|
-
MockNFLCompletedWithOvertime,
|
|
1355
|
-
10028,
|
|
1356
|
-
SportPeriodType.QUARTERS_BASED
|
|
1357
|
-
);
|
|
1358
|
-
expect(result).toBe(false);
|
|
1359
|
-
});
|
|
1360
|
-
|
|
1361
|
-
it('Should return false for 9th period typeId (period did not occur)', () => {
|
|
1362
|
-
const result = canResolveMarketsForEvent(
|
|
1363
|
-
MockNFLCompletedWithOvertime,
|
|
1364
|
-
10029,
|
|
1365
|
-
SportPeriodType.QUARTERS_BASED
|
|
1366
|
-
);
|
|
1367
|
-
expect(result).toBe(false);
|
|
1368
|
-
});
|
|
1369
|
-
|
|
1370
|
-
it('Should not include non-existent periods in batch check', () => {
|
|
1371
|
-
const typeIds = [10021, 10022, 10023, 10024, 10025, 10028, 10029];
|
|
1372
|
-
const result = filterMarketsThatCanBeResolved(
|
|
1373
|
-
MockNFLCompletedWithOvertime,
|
|
1374
|
-
typeIds,
|
|
1375
|
-
SportPeriodType.QUARTERS_BASED
|
|
1376
|
-
);
|
|
1377
|
-
|
|
1378
|
-
// Periods 1-4 are resolvable; period 5 (overtime) has no markets; periods 8-9 did not occur
|
|
1379
|
-
expect(result).toEqual([10021, 10022, 10023, 10024]);
|
|
1380
|
-
});
|
|
1381
|
-
});
|
|
1382
|
-
|
|
1383
|
-
describe('Sport-type-specific resolution for typeId 10051 (1st half)', () => {
|
|
1384
|
-
it('Soccer (HALVES_BASED): Should resolve typeId 10051 after period 1', () => {
|
|
1385
|
-
// Soccer: Period 1 = 1st half
|
|
1386
|
-
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10051, SportPeriodType.HALVES_BASED);
|
|
1387
|
-
expect(result).toBe(true);
|
|
1388
|
-
});
|
|
1389
|
-
|
|
1390
|
-
it('NFL (QUARTERS_BASED): Should resolve typeId 10051 after period 2', () => {
|
|
1391
|
-
// NFL: Period 2 completes 1st half (quarters 1+2)
|
|
1392
|
-
const result = canResolveMarketsForEvent(
|
|
1393
|
-
MockNFLLiveThirdQuarter,
|
|
1394
|
-
10051,
|
|
1395
|
-
SportPeriodType.QUARTERS_BASED
|
|
1396
|
-
);
|
|
1397
|
-
expect(result).toBe(true);
|
|
1398
|
-
});
|
|
1399
|
-
|
|
1400
|
-
it('NFL (QUARTERS_BASED): Should NOT resolve typeId 10051 after only period 1', () => {
|
|
1401
|
-
// Create mock with only period 1 complete
|
|
1402
|
-
const mockNFLFirstQuarter = {
|
|
1403
|
-
...MockNFLLiveThirdQuarter,
|
|
1404
|
-
scores: {
|
|
1405
|
-
home: { total: 7.0, periods: { period_1: 7.0 } },
|
|
1406
|
-
away: { total: 3.0, periods: { period_1: 3.0 } },
|
|
1407
|
-
},
|
|
1408
|
-
in_play: { period: '2', clock: '5:00' },
|
|
1409
|
-
};
|
|
1410
|
-
|
|
1411
|
-
const result = canResolveMarketsForEvent(mockNFLFirstQuarter, 10051, SportPeriodType.QUARTERS_BASED);
|
|
1412
|
-
expect(result).toBe(false);
|
|
1413
|
-
});
|
|
1414
|
-
|
|
1415
|
-
it('MLB (INNINGS_BASED): Should resolve typeId 10051 after period 5', () => {
|
|
1416
|
-
// MLB: Period 5 completes 1st half (innings 1-5)
|
|
1417
|
-
const result = canResolveMarketsForEvent(MockMLBLiveSixthInning, 10051, SportPeriodType.INNINGS_BASED);
|
|
1418
|
-
expect(result).toBe(true);
|
|
1419
|
-
});
|
|
1420
|
-
|
|
1421
|
-
it('MLB (INNINGS_BASED): Should NOT resolve typeId 10051 after only period 4', () => {
|
|
1422
|
-
// Create mock with only period 1-4 complete
|
|
1423
|
-
const mockMLBFourthInning = {
|
|
1424
|
-
...MockMLBLiveSixthInning,
|
|
1425
|
-
scores: {
|
|
1426
|
-
home: {
|
|
1427
|
-
total: 3.0,
|
|
1428
|
-
periods: {
|
|
1429
|
-
period_1: 0.0,
|
|
1430
|
-
period_2: 2.0,
|
|
1431
|
-
period_3: 1.0,
|
|
1432
|
-
period_4: 0.0,
|
|
1433
|
-
},
|
|
1434
|
-
},
|
|
1435
|
-
away: {
|
|
1436
|
-
total: 1.0,
|
|
1437
|
-
periods: {
|
|
1438
|
-
period_1: 1.0,
|
|
1439
|
-
period_2: 0.0,
|
|
1440
|
-
period_3: 0.0,
|
|
1441
|
-
period_4: 0.0,
|
|
1442
|
-
},
|
|
1443
|
-
},
|
|
1444
|
-
},
|
|
1445
|
-
in_play: { period: '5', clock: null },
|
|
1446
|
-
};
|
|
1447
|
-
|
|
1448
|
-
const result = canResolveMarketsForEvent(mockMLBFourthInning, 10051, SportPeriodType.INNINGS_BASED);
|
|
1449
|
-
expect(result).toBe(false);
|
|
1450
|
-
});
|
|
1451
|
-
});
|
|
1452
|
-
|
|
1453
|
-
describe('Sport-type-specific resolution for typeId 10052 (2nd half)', () => {
|
|
1454
|
-
it('Soccer (HALVES_BASED): Should resolve typeId 10052 after period 2', () => {
|
|
1455
|
-
const result = canResolveMarketsForEvent(MockSoccerCompletedEvent, 10052, SportPeriodType.HALVES_BASED);
|
|
1456
|
-
expect(result).toBe(true);
|
|
1457
|
-
});
|
|
1458
|
-
|
|
1459
|
-
it('NFL (QUARTERS_BASED): Should resolve typeId 10052 after period 4', () => {
|
|
1460
|
-
const result = canResolveMarketsForEvent(MockNFLCompletedEvent, 10052, SportPeriodType.QUARTERS_BASED);
|
|
1461
|
-
expect(result).toBe(true);
|
|
1462
|
-
});
|
|
1463
|
-
|
|
1464
|
-
it('MLB (INNINGS_BASED): Should resolve typeId 10052 after period 9', () => {
|
|
1465
|
-
const result = canResolveMarketsForEvent(MockMLBCompletedEvent, 10052, SportPeriodType.INNINGS_BASED);
|
|
1466
|
-
expect(result).toBe(true);
|
|
1467
|
-
});
|
|
1468
|
-
});
|
|
1469
|
-
|
|
1470
|
-
describe('Error handling for invalid sport type numbers', () => {
|
|
1471
|
-
it('Should throw error for invalid sport type number when using numeric parameter', () => {
|
|
1472
|
-
const typeIds = [10021, 10022];
|
|
1473
|
-
|
|
1474
|
-
// Invalid sport type number (must be 0-3)
|
|
1475
|
-
expect(() => {
|
|
1476
|
-
canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, 99);
|
|
1477
|
-
}).toThrow('Invalid sport type number: 99. Must be 0 (halves), 1 (quarters), 2 (innings), or 3 (period).');
|
|
1478
|
-
});
|
|
1479
|
-
|
|
1480
|
-
it('Should throw error for negative sport type number', () => {
|
|
1481
|
-
const typeIds = [10021];
|
|
1482
|
-
|
|
1483
|
-
expect(() => {
|
|
1484
|
-
canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, -1);
|
|
1485
|
-
}).toThrow('Invalid sport type number: -1. Must be 0 (halves), 1 (quarters), 2 (innings), or 3 (period).');
|
|
1486
|
-
});
|
|
1487
|
-
});
|
|
1488
|
-
});
|
|
1489
|
-
});
|
|
1
|
+
import {
|
|
2
|
+
detectCompletedPeriods,
|
|
3
|
+
canResolveMarketsForEvent,
|
|
4
|
+
canResolveMultipleTypeIdsForEvent,
|
|
5
|
+
filterMarketsThatCanBeResolved,
|
|
6
|
+
} from '../../utils/resolution';
|
|
7
|
+
import { SportPeriodType } from '../../types/resolution';
|
|
8
|
+
import {
|
|
9
|
+
MockSoccerCompletedEvent,
|
|
10
|
+
MockSoccerLiveSecondHalf,
|
|
11
|
+
MockSoccerLiveFirstHalf,
|
|
12
|
+
MockNFLCompletedEvent,
|
|
13
|
+
MockNFLLiveThirdQuarter,
|
|
14
|
+
MockMLBCompletedEvent,
|
|
15
|
+
MockMLBLiveSixthInning,
|
|
16
|
+
MockSoccerLiveFirstHalfInProgress,
|
|
17
|
+
MockNFLCompletedWithOvertime,
|
|
18
|
+
MockNBACompletedEvent,
|
|
19
|
+
MockNBALiveAtHalftime,
|
|
20
|
+
} from '../mock/MockOpticOddsEvents';
|
|
21
|
+
|
|
22
|
+
describe('Resolution Utils', () => {
|
|
23
|
+
describe('detectCompletedPeriods', () => {
|
|
24
|
+
it('Should detect completed periods for real completed soccer game (UEFA Europa League)', () => {
|
|
25
|
+
const result = detectCompletedPeriods(MockSoccerCompletedEvent);
|
|
26
|
+
|
|
27
|
+
expect(result).not.toBeNull();
|
|
28
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
29
|
+
expect(result?.readyForResolution).toBe(true);
|
|
30
|
+
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 0.0 });
|
|
31
|
+
expect(result?.periodScores['period2']).toEqual({ home: 1.0, away: 0.0 });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('Should detect completed first half in real live soccer game (2nd half)', () => {
|
|
35
|
+
const result = detectCompletedPeriods(MockSoccerLiveSecondHalf);
|
|
36
|
+
|
|
37
|
+
expect(result).not.toBeNull();
|
|
38
|
+
expect(result?.completedPeriods).toEqual([1]);
|
|
39
|
+
expect(result?.readyForResolution).toBe(true);
|
|
40
|
+
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 1.0 });
|
|
41
|
+
expect(result?.currentPeriod).toBe(2);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('Should return null for real live soccer game in first half (no completed periods)', () => {
|
|
45
|
+
const result = detectCompletedPeriods(MockSoccerLiveFirstHalf);
|
|
46
|
+
|
|
47
|
+
expect(result).toBeNull();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('Should detect completed periods for real completed NFL game (Patriots vs Panthers)', () => {
|
|
51
|
+
const result = detectCompletedPeriods(MockNFLCompletedEvent);
|
|
52
|
+
|
|
53
|
+
expect(result).not.toBeNull();
|
|
54
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
55
|
+
expect(result?.readyForResolution).toBe(true);
|
|
56
|
+
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 6.0 });
|
|
57
|
+
expect(result?.periodScores['period2']).toEqual({ home: 21.0, away: 0.0 });
|
|
58
|
+
expect(result?.periodScores['period3']).toEqual({ home: 7.0, away: 0.0 });
|
|
59
|
+
expect(result?.periodScores['period4']).toEqual({ home: 7.0, away: 7.0 });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('Should detect completed quarters in real live NFL game (3rd quarter)', () => {
|
|
63
|
+
const result = detectCompletedPeriods(MockNFLLiveThirdQuarter);
|
|
64
|
+
|
|
65
|
+
expect(result).not.toBeNull();
|
|
66
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
67
|
+
expect(result?.readyForResolution).toBe(true);
|
|
68
|
+
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
69
|
+
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
70
|
+
expect(result?.currentPeriod).toBe(3);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('Should detect completed innings for real completed MLB game (Tigers vs Guardians)', () => {
|
|
74
|
+
const result = detectCompletedPeriods(MockMLBCompletedEvent);
|
|
75
|
+
|
|
76
|
+
expect(result).not.toBeNull();
|
|
77
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
78
|
+
expect(result?.readyForResolution).toBe(true);
|
|
79
|
+
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 0.0 });
|
|
80
|
+
expect(result?.periodScores['period3']).toEqual({ home: 0.0, away: 1.0 });
|
|
81
|
+
expect(result?.periodScores['period7']).toEqual({ home: 0.0, away: 4.0 });
|
|
82
|
+
expect(result?.periodScores['period8']).toEqual({ home: 2.0, away: 0.0 });
|
|
83
|
+
expect(result?.periodScores['period9']).toEqual({ home: 0.0, away: 0.0 });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('Should detect completed innings in real live MLB game (6th inning)', () => {
|
|
87
|
+
const result = detectCompletedPeriods(MockMLBLiveSixthInning);
|
|
88
|
+
|
|
89
|
+
expect(result).not.toBeNull();
|
|
90
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]);
|
|
91
|
+
expect(result?.readyForResolution).toBe(true);
|
|
92
|
+
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 1.0 });
|
|
93
|
+
expect(result?.periodScores['period2']).toEqual({ home: 2.0, away: 0.0 });
|
|
94
|
+
expect(result?.periodScores['period3']).toEqual({ home: 1.0, away: 0.0 });
|
|
95
|
+
expect(result?.periodScores['period4']).toEqual({ home: 0.0, away: 0.0 });
|
|
96
|
+
expect(result?.periodScores['period5']).toEqual({ home: 1.0, away: 1.0 });
|
|
97
|
+
expect(result?.currentPeriod).toBe(6);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('Should detect completed periods for real completed NBA game (Warriors vs Suns)', () => {
|
|
101
|
+
const result = detectCompletedPeriods(MockNBACompletedEvent);
|
|
102
|
+
|
|
103
|
+
expect(result).not.toBeNull();
|
|
104
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
105
|
+
expect(result?.readyForResolution).toBe(true);
|
|
106
|
+
expect(result?.periodScores['period1']).toEqual({ home: 33.0, away: 19.0 });
|
|
107
|
+
expect(result?.periodScores['period2']).toEqual({ home: 35.0, away: 30.0 });
|
|
108
|
+
expect(result?.periodScores['period3']).toEqual({ home: 24.0, away: 34.0 });
|
|
109
|
+
expect(result?.periodScores['period4']).toEqual({ home: 26.0, away: 24.0 });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('Should detect completed quarters at halftime for real NBA game (Warriors vs Suns)', () => {
|
|
113
|
+
const result = detectCompletedPeriods(MockNBALiveAtHalftime);
|
|
114
|
+
|
|
115
|
+
expect(result).not.toBeNull();
|
|
116
|
+
expect(result?.completedPeriods).toEqual([1, 2]); // Both quarters complete at halftime
|
|
117
|
+
expect(result?.readyForResolution).toBe(true);
|
|
118
|
+
expect(result?.periodScores['period1']).toEqual({ home: 33.0, away: 19.0 });
|
|
119
|
+
expect(result?.periodScores['period2']).toEqual({ home: 35.0, away: 30.0 });
|
|
120
|
+
expect(result?.currentPeriod).toBe(2); // Highest period with data at halftime
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('Should return null for real live soccer game with non-numeric period indicator (1H)', () => {
|
|
124
|
+
const result = detectCompletedPeriods(MockSoccerLiveFirstHalfInProgress);
|
|
125
|
+
|
|
126
|
+
// Period 1 exists in data but period is "1H" (non-numeric) meaning first half is still in progress
|
|
127
|
+
// Period 1 is NOT complete until we see period_2 in the data or status becomes completed
|
|
128
|
+
expect(result).toBeNull();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('Should detect completed periods for completed American Football game', () => {
|
|
132
|
+
const event = {
|
|
133
|
+
sport: {
|
|
134
|
+
id: 'football',
|
|
135
|
+
name: 'Football',
|
|
136
|
+
},
|
|
137
|
+
fixture: {
|
|
138
|
+
id: '20250930BEED03AA',
|
|
139
|
+
status: 'completed',
|
|
140
|
+
is_live: false,
|
|
141
|
+
},
|
|
142
|
+
scores: {
|
|
143
|
+
home: {
|
|
144
|
+
total: 28.0,
|
|
145
|
+
periods: {
|
|
146
|
+
period_1: 7.0,
|
|
147
|
+
period_2: 14.0,
|
|
148
|
+
period_3: 0.0,
|
|
149
|
+
period_4: 7.0,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
away: {
|
|
153
|
+
total: 3.0,
|
|
154
|
+
periods: {
|
|
155
|
+
period_1: 3.0,
|
|
156
|
+
period_2: 0.0,
|
|
157
|
+
period_3: 0.0,
|
|
158
|
+
period_4: 0.0,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
in_play: {
|
|
163
|
+
period: '4',
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const result = detectCompletedPeriods(event);
|
|
168
|
+
|
|
169
|
+
expect(result).not.toBeNull();
|
|
170
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
171
|
+
expect(result?.readyForResolution).toBe(true);
|
|
172
|
+
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
173
|
+
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 0.0 });
|
|
174
|
+
expect(result?.periodScores['period3']).toEqual({ home: 0.0, away: 0.0 });
|
|
175
|
+
expect(result?.periodScores['period4']).toEqual({ home: 7.0, away: 0.0 });
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('Should detect completed quarters in live American Football game', () => {
|
|
179
|
+
const event = {
|
|
180
|
+
sport: {
|
|
181
|
+
name: 'Football',
|
|
182
|
+
},
|
|
183
|
+
fixture: {
|
|
184
|
+
id: 'nfl-live-123',
|
|
185
|
+
status: 'live',
|
|
186
|
+
is_live: true,
|
|
187
|
+
},
|
|
188
|
+
scores: {
|
|
189
|
+
home: {
|
|
190
|
+
total: 21.0,
|
|
191
|
+
periods: {
|
|
192
|
+
period_1: 7.0,
|
|
193
|
+
period_2: 14.0,
|
|
194
|
+
period_3: 0.0,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
away: {
|
|
198
|
+
total: 10.0,
|
|
199
|
+
periods: {
|
|
200
|
+
period_1: 3.0,
|
|
201
|
+
period_2: 7.0,
|
|
202
|
+
period_3: 0.0,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
in_play: {
|
|
207
|
+
period: '3',
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const result = detectCompletedPeriods(event);
|
|
212
|
+
|
|
213
|
+
expect(result).not.toBeNull();
|
|
214
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
215
|
+
expect(result?.readyForResolution).toBe(true);
|
|
216
|
+
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
217
|
+
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
218
|
+
expect(result?.currentPeriod).toBe(3);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('Should detect all quarters complete in American Football overtime', () => {
|
|
222
|
+
const event = {
|
|
223
|
+
sport: {
|
|
224
|
+
name: 'American Football',
|
|
225
|
+
},
|
|
226
|
+
fixture: {
|
|
227
|
+
id: 'nfl-ot-456',
|
|
228
|
+
status: 'live',
|
|
229
|
+
is_live: true,
|
|
230
|
+
},
|
|
231
|
+
scores: {
|
|
232
|
+
home: {
|
|
233
|
+
total: 24.0,
|
|
234
|
+
periods: {
|
|
235
|
+
period_1: 7.0,
|
|
236
|
+
period_2: 7.0,
|
|
237
|
+
period_3: 3.0,
|
|
238
|
+
period_4: 7.0,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
away: {
|
|
242
|
+
total: 24.0,
|
|
243
|
+
periods: {
|
|
244
|
+
period_1: 10.0,
|
|
245
|
+
period_2: 7.0,
|
|
246
|
+
period_3: 0.0,
|
|
247
|
+
period_4: 7.0,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
in_play: {
|
|
252
|
+
period: 'overtime',
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const result = detectCompletedPeriods(event);
|
|
257
|
+
|
|
258
|
+
expect(result).not.toBeNull();
|
|
259
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
260
|
+
expect(result?.readyForResolution).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('Should detect completed first half in live soccer game', () => {
|
|
264
|
+
const event = {
|
|
265
|
+
sport: {
|
|
266
|
+
name: 'Soccer',
|
|
267
|
+
},
|
|
268
|
+
fixture: {
|
|
269
|
+
id: 'soccer-123',
|
|
270
|
+
status: 'live',
|
|
271
|
+
is_live: true,
|
|
272
|
+
},
|
|
273
|
+
scores: {
|
|
274
|
+
home: {
|
|
275
|
+
total: 2.0,
|
|
276
|
+
periods: {
|
|
277
|
+
period_1: 1.0,
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
away: {
|
|
281
|
+
total: 1.0,
|
|
282
|
+
periods: {
|
|
283
|
+
period_1: 0.0,
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
in_play: {
|
|
288
|
+
period: '2',
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const result = detectCompletedPeriods(event);
|
|
293
|
+
|
|
294
|
+
expect(result).not.toBeNull();
|
|
295
|
+
expect(result?.completedPeriods).toEqual([1]);
|
|
296
|
+
expect(result?.readyForResolution).toBe(true);
|
|
297
|
+
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 0.0 });
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('Should detect completed quarters in live basketball game', () => {
|
|
301
|
+
const event = {
|
|
302
|
+
sport: {
|
|
303
|
+
name: 'Basketball',
|
|
304
|
+
},
|
|
305
|
+
fixture: {
|
|
306
|
+
id: 'nba-789',
|
|
307
|
+
status: 'live',
|
|
308
|
+
is_live: true,
|
|
309
|
+
},
|
|
310
|
+
scores: {
|
|
311
|
+
home: {
|
|
312
|
+
total: 65.0,
|
|
313
|
+
periods: {
|
|
314
|
+
period_1: 25.0,
|
|
315
|
+
period_2: 20.0,
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
away: {
|
|
319
|
+
total: 62.0,
|
|
320
|
+
periods: {
|
|
321
|
+
period_1: 22.0,
|
|
322
|
+
period_2: 18.0,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
in_play: {
|
|
327
|
+
period: '3',
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const result = detectCompletedPeriods(event);
|
|
332
|
+
|
|
333
|
+
expect(result).not.toBeNull();
|
|
334
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
335
|
+
expect(result?.readyForResolution).toBe(true);
|
|
336
|
+
expect(result?.periodScores['period1']).toEqual({ home: 25.0, away: 22.0 });
|
|
337
|
+
expect(result?.periodScores['period2']).toEqual({ home: 20.0, away: 18.0 });
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('Should detect completed sets in live tennis match', () => {
|
|
341
|
+
const event = {
|
|
342
|
+
sport: {
|
|
343
|
+
name: 'Tennis',
|
|
344
|
+
},
|
|
345
|
+
fixture: {
|
|
346
|
+
id: 'tennis-101',
|
|
347
|
+
status: 'live',
|
|
348
|
+
is_live: true,
|
|
349
|
+
},
|
|
350
|
+
scores: {
|
|
351
|
+
home: {
|
|
352
|
+
total: 1.0,
|
|
353
|
+
periods: {
|
|
354
|
+
period_1: 6.0,
|
|
355
|
+
period_2: 3.0,
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
away: {
|
|
359
|
+
total: 1.0,
|
|
360
|
+
periods: {
|
|
361
|
+
period_1: 4.0,
|
|
362
|
+
period_2: 6.0,
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
in_play: {
|
|
367
|
+
period: '3',
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
const result = detectCompletedPeriods(event);
|
|
372
|
+
|
|
373
|
+
expect(result).not.toBeNull();
|
|
374
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
375
|
+
expect(result?.readyForResolution).toBe(true);
|
|
376
|
+
expect(result?.periodScores['period1']).toEqual({ home: 6.0, away: 4.0 });
|
|
377
|
+
expect(result?.periodScores['period2']).toEqual({ home: 3.0, away: 6.0 });
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('Should detect completed periods in live hockey game', () => {
|
|
381
|
+
const event = {
|
|
382
|
+
sport: {
|
|
383
|
+
name: 'Ice Hockey',
|
|
384
|
+
},
|
|
385
|
+
fixture: {
|
|
386
|
+
id: 'nhl-202',
|
|
387
|
+
status: 'live',
|
|
388
|
+
is_live: true,
|
|
389
|
+
},
|
|
390
|
+
scores: {
|
|
391
|
+
home: {
|
|
392
|
+
total: 2.0,
|
|
393
|
+
periods: {
|
|
394
|
+
period_1: 1.0,
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
away: {
|
|
398
|
+
total: 1.0,
|
|
399
|
+
periods: {
|
|
400
|
+
period_1: 1.0,
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
in_play: {
|
|
405
|
+
period: '2',
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const result = detectCompletedPeriods(event);
|
|
410
|
+
|
|
411
|
+
expect(result).not.toBeNull();
|
|
412
|
+
expect(result?.completedPeriods).toEqual([1]);
|
|
413
|
+
expect(result?.readyForResolution).toBe(true);
|
|
414
|
+
expect(result?.periodScores['period1']).toEqual({ home: 1.0, away: 1.0 });
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('Should detect all periods complete in hockey overtime', () => {
|
|
418
|
+
const event = {
|
|
419
|
+
sport: {
|
|
420
|
+
name: 'Hockey',
|
|
421
|
+
},
|
|
422
|
+
fixture: {
|
|
423
|
+
id: 'nhl-303',
|
|
424
|
+
status: 'live',
|
|
425
|
+
is_live: true,
|
|
426
|
+
},
|
|
427
|
+
scores: {
|
|
428
|
+
home: {
|
|
429
|
+
total: 3.0,
|
|
430
|
+
periods: {
|
|
431
|
+
period_1: 1.0,
|
|
432
|
+
period_2: 1.0,
|
|
433
|
+
period_3: 1.0,
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
away: {
|
|
437
|
+
total: 3.0,
|
|
438
|
+
periods: {
|
|
439
|
+
period_1: 0.0,
|
|
440
|
+
period_2: 2.0,
|
|
441
|
+
period_3: 1.0,
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
in_play: {
|
|
446
|
+
period: 'overtime',
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
const result = detectCompletedPeriods(event);
|
|
451
|
+
|
|
452
|
+
expect(result).not.toBeNull();
|
|
453
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3]);
|
|
454
|
+
expect(result?.readyForResolution).toBe(true);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('Should detect completed innings in live baseball game', () => {
|
|
458
|
+
const event = {
|
|
459
|
+
sport: {
|
|
460
|
+
name: 'Baseball',
|
|
461
|
+
},
|
|
462
|
+
fixture: {
|
|
463
|
+
id: 'mlb-404',
|
|
464
|
+
status: 'live',
|
|
465
|
+
is_live: true,
|
|
466
|
+
},
|
|
467
|
+
scores: {
|
|
468
|
+
home: {
|
|
469
|
+
total: 4.0,
|
|
470
|
+
periods: {
|
|
471
|
+
period_1: 1.0,
|
|
472
|
+
period_2: 0.0,
|
|
473
|
+
period_3: 2.0,
|
|
474
|
+
period_4: 1.0,
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
away: {
|
|
478
|
+
total: 3.0,
|
|
479
|
+
periods: {
|
|
480
|
+
period_1: 0.0,
|
|
481
|
+
period_2: 1.0,
|
|
482
|
+
period_3: 1.0,
|
|
483
|
+
period_4: 1.0,
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
},
|
|
487
|
+
in_play: {
|
|
488
|
+
period: '5',
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const result = detectCompletedPeriods(event);
|
|
493
|
+
|
|
494
|
+
expect(result).not.toBeNull();
|
|
495
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
496
|
+
expect(result?.readyForResolution).toBe(true);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('Should detect completed sets in live volleyball match', () => {
|
|
500
|
+
const event = {
|
|
501
|
+
sport: {
|
|
502
|
+
name: 'Volleyball',
|
|
503
|
+
},
|
|
504
|
+
fixture: {
|
|
505
|
+
id: 'volleyball-505',
|
|
506
|
+
status: 'live',
|
|
507
|
+
is_live: true,
|
|
508
|
+
},
|
|
509
|
+
scores: {
|
|
510
|
+
home: {
|
|
511
|
+
total: 1.0,
|
|
512
|
+
periods: {
|
|
513
|
+
period_1: 25.0,
|
|
514
|
+
period_2: 22.0,
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
away: {
|
|
518
|
+
total: 1.0,
|
|
519
|
+
periods: {
|
|
520
|
+
period_1: 23.0,
|
|
521
|
+
period_2: 25.0,
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
in_play: {
|
|
526
|
+
period: '3',
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const result = detectCompletedPeriods(event);
|
|
531
|
+
|
|
532
|
+
expect(result).not.toBeNull();
|
|
533
|
+
expect(result?.completedPeriods).toEqual([1, 2]);
|
|
534
|
+
expect(result?.readyForResolution).toBe(true);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('Should return null for game not started', () => {
|
|
538
|
+
const event = {
|
|
539
|
+
sport: {
|
|
540
|
+
name: 'Soccer',
|
|
541
|
+
},
|
|
542
|
+
fixture: {
|
|
543
|
+
id: 'future-game',
|
|
544
|
+
status: 'scheduled',
|
|
545
|
+
is_live: false,
|
|
546
|
+
},
|
|
547
|
+
scores: {
|
|
548
|
+
home: {
|
|
549
|
+
total: 0.0,
|
|
550
|
+
periods: {},
|
|
551
|
+
},
|
|
552
|
+
away: {
|
|
553
|
+
total: 0.0,
|
|
554
|
+
periods: {},
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
in_play: {
|
|
558
|
+
period: null,
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
const result = detectCompletedPeriods(event);
|
|
563
|
+
|
|
564
|
+
expect(result).toBeNull();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('Should return null for game in first period with no completed periods', () => {
|
|
568
|
+
const event = {
|
|
569
|
+
sport: {
|
|
570
|
+
name: 'Basketball',
|
|
571
|
+
},
|
|
572
|
+
fixture: {
|
|
573
|
+
id: 'early-game',
|
|
574
|
+
status: 'live',
|
|
575
|
+
is_live: true,
|
|
576
|
+
},
|
|
577
|
+
scores: {
|
|
578
|
+
home: {
|
|
579
|
+
total: 12.0,
|
|
580
|
+
periods: {},
|
|
581
|
+
},
|
|
582
|
+
away: {
|
|
583
|
+
total: 10.0,
|
|
584
|
+
periods: {},
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
in_play: {
|
|
588
|
+
period: '1',
|
|
589
|
+
},
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const result = detectCompletedPeriods(event);
|
|
593
|
+
|
|
594
|
+
expect(result).toBeNull();
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('Bug Fix: Should NOT mark current period as complete when future periods have data (live in period 3)', () => {
|
|
598
|
+
// This tests the bug where OpticOdds API includes future period data with scores (including zeros)
|
|
599
|
+
// Period 3 is currently being played, period 4 has 0-0 scores but hasn't started
|
|
600
|
+
const event = {
|
|
601
|
+
status: 'live',
|
|
602
|
+
is_live: true,
|
|
603
|
+
in_play: { period: '3' },
|
|
604
|
+
scores: {
|
|
605
|
+
home: { periods: { period_1: 33, period_2: 32, period_3: 12, period_4: 0 } },
|
|
606
|
+
away: { periods: { period_1: 23, period_2: 27, period_3: 18, period_4: 0 } },
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const result = detectCompletedPeriods(event);
|
|
611
|
+
|
|
612
|
+
expect(result).not.toBeNull();
|
|
613
|
+
expect(result?.completedPeriods).toEqual([1, 2]); // Only periods 1 and 2 are complete
|
|
614
|
+
expect(result?.currentPeriod).toBe(3);
|
|
615
|
+
// Period 3 is currently being played, so NOT complete
|
|
616
|
+
// Period 4 has data (0-0) but hasn't started, so NOT complete
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it('Bug Fix: Should mark all periods as complete for completed game', () => {
|
|
620
|
+
const event = {
|
|
621
|
+
status: 'completed',
|
|
622
|
+
is_live: false,
|
|
623
|
+
scores: {
|
|
624
|
+
home: { periods: { period_1: 33, period_2: 32, period_3: 25, period_4: 20 } },
|
|
625
|
+
away: { periods: { period_1: 23, period_2: 27, period_3: 18, period_4: 22 } },
|
|
626
|
+
},
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const result = detectCompletedPeriods(event);
|
|
630
|
+
|
|
631
|
+
expect(result).not.toBeNull();
|
|
632
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4]);
|
|
633
|
+
// All periods complete because game status is 'completed'
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
it('Bug Fix: Should NOT mark current period as complete during transition to period 4', () => {
|
|
637
|
+
const event = {
|
|
638
|
+
status: 'live',
|
|
639
|
+
is_live: true,
|
|
640
|
+
in_play: { period: '4' },
|
|
641
|
+
scores: {
|
|
642
|
+
home: { periods: { period_1: 33, period_2: 32, period_3: 25, period_4: 5 } },
|
|
643
|
+
away: { periods: { period_1: 23, period_2: 27, period_3: 18, period_4: 3 } },
|
|
644
|
+
},
|
|
645
|
+
};
|
|
646
|
+
|
|
647
|
+
const result = detectCompletedPeriods(event);
|
|
648
|
+
|
|
649
|
+
expect(result).not.toBeNull();
|
|
650
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3]); // Periods 1, 2, 3 are complete
|
|
651
|
+
expect(result?.currentPeriod).toBe(4);
|
|
652
|
+
// Period 4 is currently in_play, so NOT complete yet
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it('Basketball at halftime should mark periods 1 AND 2 as complete (quarters-based)', () => {
|
|
656
|
+
const event = {
|
|
657
|
+
sport: {
|
|
658
|
+
id: 'basketball',
|
|
659
|
+
name: 'Basketball',
|
|
660
|
+
},
|
|
661
|
+
fixture: {
|
|
662
|
+
id: 'nba-halftime-123',
|
|
663
|
+
status: 'half',
|
|
664
|
+
is_live: true,
|
|
665
|
+
},
|
|
666
|
+
scores: {
|
|
667
|
+
home: {
|
|
668
|
+
total: 52.0,
|
|
669
|
+
periods: {
|
|
670
|
+
period_1: 25.0,
|
|
671
|
+
period_2: 27.0,
|
|
672
|
+
},
|
|
673
|
+
},
|
|
674
|
+
away: {
|
|
675
|
+
total: 48.0,
|
|
676
|
+
periods: {
|
|
677
|
+
period_1: 22.0,
|
|
678
|
+
period_2: 26.0,
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
},
|
|
682
|
+
in_play: {
|
|
683
|
+
period: 'half',
|
|
684
|
+
},
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
const result = detectCompletedPeriods(event);
|
|
688
|
+
|
|
689
|
+
expect(result).not.toBeNull();
|
|
690
|
+
expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
|
|
691
|
+
expect(result?.readyForResolution).toBe(true);
|
|
692
|
+
expect(result?.periodScores['period1']).toEqual({ home: 25.0, away: 22.0 });
|
|
693
|
+
expect(result?.periodScores['period2']).toEqual({ home: 27.0, away: 26.0 });
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it('Basketball at halftime with status "halftime" should mark periods 1 AND 2 as complete', () => {
|
|
697
|
+
const event = {
|
|
698
|
+
sport: {
|
|
699
|
+
id: 'basketball',
|
|
700
|
+
name: 'Basketball',
|
|
701
|
+
},
|
|
702
|
+
fixture: {
|
|
703
|
+
id: 'nba-halftime-456',
|
|
704
|
+
status: 'halftime',
|
|
705
|
+
is_live: true,
|
|
706
|
+
},
|
|
707
|
+
scores: {
|
|
708
|
+
home: {
|
|
709
|
+
total: 55.0,
|
|
710
|
+
periods: {
|
|
711
|
+
period_1: 28.0,
|
|
712
|
+
period_2: 27.0,
|
|
713
|
+
},
|
|
714
|
+
},
|
|
715
|
+
away: {
|
|
716
|
+
total: 50.0,
|
|
717
|
+
periods: {
|
|
718
|
+
period_1: 24.0,
|
|
719
|
+
period_2: 26.0,
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
},
|
|
723
|
+
in_play: {
|
|
724
|
+
period: '2',
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const result = detectCompletedPeriods(event);
|
|
729
|
+
|
|
730
|
+
expect(result).not.toBeNull();
|
|
731
|
+
expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
|
|
732
|
+
expect(result?.readyForResolution).toBe(true);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it('Football at halftime should mark periods 1 AND 2 as complete (quarters-based)', () => {
|
|
736
|
+
const event = {
|
|
737
|
+
sport: {
|
|
738
|
+
id: 'football',
|
|
739
|
+
name: 'Football',
|
|
740
|
+
},
|
|
741
|
+
fixture: {
|
|
742
|
+
id: 'nfl-halftime-789',
|
|
743
|
+
status: 'half',
|
|
744
|
+
is_live: true,
|
|
745
|
+
},
|
|
746
|
+
scores: {
|
|
747
|
+
home: {
|
|
748
|
+
total: 21.0,
|
|
749
|
+
periods: {
|
|
750
|
+
period_1: 7.0,
|
|
751
|
+
period_2: 14.0,
|
|
752
|
+
},
|
|
753
|
+
},
|
|
754
|
+
away: {
|
|
755
|
+
total: 10.0,
|
|
756
|
+
periods: {
|
|
757
|
+
period_1: 3.0,
|
|
758
|
+
period_2: 7.0,
|
|
759
|
+
},
|
|
760
|
+
},
|
|
761
|
+
},
|
|
762
|
+
in_play: {
|
|
763
|
+
period: 'half',
|
|
764
|
+
},
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
const result = detectCompletedPeriods(event);
|
|
768
|
+
|
|
769
|
+
expect(result).not.toBeNull();
|
|
770
|
+
expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
|
|
771
|
+
expect(result?.readyForResolution).toBe(true);
|
|
772
|
+
expect(result?.periodScores['period1']).toEqual({ home: 7.0, away: 3.0 });
|
|
773
|
+
expect(result?.periodScores['period2']).toEqual({ home: 14.0, away: 7.0 });
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it('Football at halftime with status "halftime" should mark periods 1 AND 2 as complete', () => {
|
|
777
|
+
const event = {
|
|
778
|
+
sport: {
|
|
779
|
+
id: 'football',
|
|
780
|
+
name: 'American Football',
|
|
781
|
+
},
|
|
782
|
+
fixture: {
|
|
783
|
+
id: 'nfl-halftime-890',
|
|
784
|
+
status: 'halftime',
|
|
785
|
+
is_live: true,
|
|
786
|
+
},
|
|
787
|
+
scores: {
|
|
788
|
+
home: {
|
|
789
|
+
total: 17.0,
|
|
790
|
+
periods: {
|
|
791
|
+
period_1: 10.0,
|
|
792
|
+
period_2: 7.0,
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
away: {
|
|
796
|
+
total: 14.0,
|
|
797
|
+
periods: {
|
|
798
|
+
period_1: 7.0,
|
|
799
|
+
period_2: 7.0,
|
|
800
|
+
},
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
in_play: {
|
|
804
|
+
period: '2',
|
|
805
|
+
},
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
const result = detectCompletedPeriods(event);
|
|
809
|
+
|
|
810
|
+
expect(result).not.toBeNull();
|
|
811
|
+
expect(result?.completedPeriods).toEqual([1, 2]); // Both Q1 and Q2 complete at halftime
|
|
812
|
+
expect(result?.readyForResolution).toBe(true);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('Baseball at halftime should mark periods 1-5 as complete (innings-based)', () => {
|
|
816
|
+
const event = {
|
|
817
|
+
sport: {
|
|
818
|
+
id: 'baseball',
|
|
819
|
+
name: 'Baseball',
|
|
820
|
+
},
|
|
821
|
+
fixture: {
|
|
822
|
+
id: 'mlb-halftime-789',
|
|
823
|
+
status: 'half',
|
|
824
|
+
is_live: true,
|
|
825
|
+
},
|
|
826
|
+
scores: {
|
|
827
|
+
home: {
|
|
828
|
+
total: 3.0,
|
|
829
|
+
periods: {
|
|
830
|
+
period_1: 0.0,
|
|
831
|
+
period_2: 1.0,
|
|
832
|
+
period_3: 1.0,
|
|
833
|
+
period_4: 0.0,
|
|
834
|
+
period_5: 1.0,
|
|
835
|
+
},
|
|
836
|
+
},
|
|
837
|
+
away: {
|
|
838
|
+
total: 2.0,
|
|
839
|
+
periods: {
|
|
840
|
+
period_1: 1.0,
|
|
841
|
+
period_2: 0.0,
|
|
842
|
+
period_3: 0.0,
|
|
843
|
+
period_4: 1.0,
|
|
844
|
+
period_5: 0.0,
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
},
|
|
848
|
+
in_play: {
|
|
849
|
+
period: 'half',
|
|
850
|
+
},
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
const result = detectCompletedPeriods(event);
|
|
854
|
+
|
|
855
|
+
expect(result).not.toBeNull();
|
|
856
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]); // First 5 innings complete at halftime
|
|
857
|
+
expect(result?.readyForResolution).toBe(true);
|
|
858
|
+
expect(result?.periodScores['period1']).toEqual({ home: 0.0, away: 1.0 });
|
|
859
|
+
expect(result?.periodScores['period5']).toEqual({ home: 1.0, away: 0.0 });
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it('Soccer at halftime should mark only period 1 as complete (halves-based)', () => {
|
|
863
|
+
const event = {
|
|
864
|
+
sport: {
|
|
865
|
+
id: 'soccer',
|
|
866
|
+
name: 'Soccer',
|
|
867
|
+
},
|
|
868
|
+
fixture: {
|
|
869
|
+
id: 'soccer-halftime-101',
|
|
870
|
+
status: 'halftime',
|
|
871
|
+
is_live: true,
|
|
872
|
+
},
|
|
873
|
+
scores: {
|
|
874
|
+
home: {
|
|
875
|
+
total: 2.0,
|
|
876
|
+
periods: {
|
|
877
|
+
period_1: 2.0,
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
away: {
|
|
881
|
+
total: 1.0,
|
|
882
|
+
periods: {
|
|
883
|
+
period_1: 1.0,
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
in_play: {
|
|
888
|
+
period: 'half',
|
|
889
|
+
},
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
const result = detectCompletedPeriods(event);
|
|
893
|
+
|
|
894
|
+
expect(result).not.toBeNull();
|
|
895
|
+
expect(result?.completedPeriods).toEqual([1]); // Only first half complete at halftime
|
|
896
|
+
expect(result?.readyForResolution).toBe(true);
|
|
897
|
+
expect(result?.periodScores['period1']).toEqual({ home: 2.0, away: 1.0 });
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it('Hockey at halftime should NOT mark any periods as complete (period-based, no halftime concept)', () => {
|
|
901
|
+
const event = {
|
|
902
|
+
sport: {
|
|
903
|
+
id: 'hockey',
|
|
904
|
+
name: 'Hockey',
|
|
905
|
+
},
|
|
906
|
+
fixture: {
|
|
907
|
+
id: 'nhl-halftime-202',
|
|
908
|
+
status: 'half',
|
|
909
|
+
is_live: true,
|
|
910
|
+
},
|
|
911
|
+
scores: {
|
|
912
|
+
home: {
|
|
913
|
+
total: 2.0,
|
|
914
|
+
periods: {
|
|
915
|
+
period_1: 1.0,
|
|
916
|
+
period_2: 1.0,
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
away: {
|
|
920
|
+
total: 1.0,
|
|
921
|
+
periods: {
|
|
922
|
+
period_1: 0.0,
|
|
923
|
+
period_2: 1.0,
|
|
924
|
+
},
|
|
925
|
+
},
|
|
926
|
+
},
|
|
927
|
+
in_play: {
|
|
928
|
+
period: 'half',
|
|
929
|
+
},
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
const result = detectCompletedPeriods(event);
|
|
933
|
+
|
|
934
|
+
// Hockey doesn't have traditional halftime, so halftime status shouldn't mark periods complete
|
|
935
|
+
// Periods should only be marked complete based on in_play.period progression
|
|
936
|
+
expect(result).toBeNull();
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
it('Unknown sport at halftime should default to PERIOD_BASED (no halftime processing)', () => {
|
|
940
|
+
const event = {
|
|
941
|
+
sport: {
|
|
942
|
+
id: 'unknown_sport',
|
|
943
|
+
name: 'Unknown Sport',
|
|
944
|
+
},
|
|
945
|
+
fixture: {
|
|
946
|
+
id: 'unknown-halftime-123',
|
|
947
|
+
status: 'half',
|
|
948
|
+
is_live: true,
|
|
949
|
+
},
|
|
950
|
+
scores: {
|
|
951
|
+
home: {
|
|
952
|
+
total: 50.0,
|
|
953
|
+
periods: {
|
|
954
|
+
period_1: 25.0,
|
|
955
|
+
period_2: 25.0,
|
|
956
|
+
},
|
|
957
|
+
},
|
|
958
|
+
away: {
|
|
959
|
+
total: 40.0,
|
|
960
|
+
periods: {
|
|
961
|
+
period_1: 20.0,
|
|
962
|
+
period_2: 20.0,
|
|
963
|
+
},
|
|
964
|
+
},
|
|
965
|
+
},
|
|
966
|
+
in_play: {
|
|
967
|
+
period: 'half',
|
|
968
|
+
},
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
const result = detectCompletedPeriods(event);
|
|
972
|
+
|
|
973
|
+
// Unknown sports default to PERIOD_BASED (no halftime processing)
|
|
974
|
+
// So halftime status should NOT mark any periods as complete
|
|
975
|
+
expect(result).toBeNull();
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
it('Event with missing sport.id should default to PERIOD_BASED (no halftime processing)', () => {
|
|
979
|
+
const event = {
|
|
980
|
+
sport: {
|
|
981
|
+
name: 'Some Sport',
|
|
982
|
+
},
|
|
983
|
+
fixture: {
|
|
984
|
+
id: 'no-sport-id-123',
|
|
985
|
+
status: 'half',
|
|
986
|
+
is_live: true,
|
|
987
|
+
},
|
|
988
|
+
scores: {
|
|
989
|
+
home: {
|
|
990
|
+
total: 50.0,
|
|
991
|
+
periods: {
|
|
992
|
+
period_1: 25.0,
|
|
993
|
+
period_2: 25.0,
|
|
994
|
+
},
|
|
995
|
+
},
|
|
996
|
+
away: {
|
|
997
|
+
total: 40.0,
|
|
998
|
+
periods: {
|
|
999
|
+
period_1: 20.0,
|
|
1000
|
+
period_2: 20.0,
|
|
1001
|
+
},
|
|
1002
|
+
},
|
|
1003
|
+
},
|
|
1004
|
+
in_play: {
|
|
1005
|
+
period: 'half',
|
|
1006
|
+
},
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
const result = detectCompletedPeriods(event);
|
|
1010
|
+
|
|
1011
|
+
// Missing sport.id defaults to PERIOD_BASED (no halftime processing)
|
|
1012
|
+
expect(result).toBeNull();
|
|
1013
|
+
});
|
|
1014
|
+
});
|
|
1015
|
+
|
|
1016
|
+
describe('canResolveMarketsForEvent', () => {
|
|
1017
|
+
describe('Single typeId checks', () => {
|
|
1018
|
+
it('Should return true for 1st period typeId when period 1 is complete (Soccer 2nd half)', () => {
|
|
1019
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10021, SportPeriodType.HALVES_BASED);
|
|
1020
|
+
expect(result).toBe(true);
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
it('Should return false for 2nd period typeId when only period 1 is complete', () => {
|
|
1024
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10022, SportPeriodType.HALVES_BASED);
|
|
1025
|
+
expect(result).toBe(false);
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
it('Should return false for full game typeId during live game', () => {
|
|
1029
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10001, SportPeriodType.HALVES_BASED);
|
|
1030
|
+
expect(result).toBe(false);
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
it('Should return true for full game typeId when game is completed', () => {
|
|
1034
|
+
const result = canResolveMarketsForEvent(MockSoccerCompletedEvent, 10001, SportPeriodType.HALVES_BASED);
|
|
1035
|
+
expect(result).toBe(true);
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
it('Should return true for 1st quarter typeId when quarter 1 complete (NFL)', () => {
|
|
1039
|
+
const result = canResolveMarketsForEvent(
|
|
1040
|
+
MockNFLLiveThirdQuarter,
|
|
1041
|
+
10021,
|
|
1042
|
+
SportPeriodType.QUARTERS_BASED
|
|
1043
|
+
);
|
|
1044
|
+
expect(result).toBe(true);
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
it('Should return true for 2nd quarter typeId when quarters 1-2 complete (NFL)', () => {
|
|
1048
|
+
const result = canResolveMarketsForEvent(
|
|
1049
|
+
MockNFLLiveThirdQuarter,
|
|
1050
|
+
10022,
|
|
1051
|
+
SportPeriodType.QUARTERS_BASED
|
|
1052
|
+
);
|
|
1053
|
+
expect(result).toBe(true);
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
it('Should return false for 3rd quarter typeId during 3rd quarter (NFL)', () => {
|
|
1057
|
+
const result = canResolveMarketsForEvent(
|
|
1058
|
+
MockNFLLiveThirdQuarter,
|
|
1059
|
+
10023,
|
|
1060
|
+
SportPeriodType.QUARTERS_BASED
|
|
1061
|
+
);
|
|
1062
|
+
expect(result).toBe(false);
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
it('Should return true for all quarter typeIds when game is completed (NFL)', () => {
|
|
1066
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10021, SportPeriodType.QUARTERS_BASED)).toBe(
|
|
1067
|
+
true
|
|
1068
|
+
);
|
|
1069
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10022, SportPeriodType.QUARTERS_BASED)).toBe(
|
|
1070
|
+
true
|
|
1071
|
+
);
|
|
1072
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10023, SportPeriodType.QUARTERS_BASED)).toBe(
|
|
1073
|
+
true
|
|
1074
|
+
);
|
|
1075
|
+
expect(canResolveMarketsForEvent(MockNFLCompletedEvent, 10024, SportPeriodType.QUARTERS_BASED)).toBe(
|
|
1076
|
+
true
|
|
1077
|
+
);
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
it('Should return false when no periods are complete', () => {
|
|
1081
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveFirstHalf, 10021, SportPeriodType.HALVES_BASED);
|
|
1082
|
+
expect(result).toBe(false);
|
|
1083
|
+
});
|
|
1084
|
+
|
|
1085
|
+
it('Should return false for non-existent typeId', () => {
|
|
1086
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 99999, SportPeriodType.HALVES_BASED);
|
|
1087
|
+
expect(result).toBe(false);
|
|
1088
|
+
});
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
describe('Batch typeIds checks', () => {
|
|
1092
|
+
it('Should return only resolvable typeIds for live soccer in 2nd half', () => {
|
|
1093
|
+
const typeIds = [10021, 10022, 10031, 10001];
|
|
1094
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1095
|
+
MockSoccerLiveSecondHalf,
|
|
1096
|
+
typeIds,
|
|
1097
|
+
SportPeriodType.HALVES_BASED
|
|
1098
|
+
);
|
|
1099
|
+
|
|
1100
|
+
expect(result).toEqual([10021, 10031]); // Only period 1 typeIds
|
|
1101
|
+
});
|
|
1102
|
+
|
|
1103
|
+
it('Should exclude full game typeIds during live game', () => {
|
|
1104
|
+
const typeIds = [10021, 10001, 10002, 10003];
|
|
1105
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1106
|
+
MockNFLLiveThirdQuarter,
|
|
1107
|
+
typeIds,
|
|
1108
|
+
SportPeriodType.QUARTERS_BASED
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
expect(result).toEqual([10021]); // Full game typeIds excluded
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
it('Should include full game typeIds when game is completed', () => {
|
|
1115
|
+
const typeIds = [10021, 10022, 10001, 10002];
|
|
1116
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1117
|
+
MockSoccerCompletedEvent,
|
|
1118
|
+
typeIds,
|
|
1119
|
+
SportPeriodType.HALVES_BASED
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
expect(result).toEqual([10021, 10022, 10001, 10002]);
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
it('Should return empty array when no typeIds are resolvable', () => {
|
|
1126
|
+
const typeIds = [10022, 10023, 10024];
|
|
1127
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1128
|
+
MockSoccerLiveSecondHalf,
|
|
1129
|
+
typeIds,
|
|
1130
|
+
SportPeriodType.HALVES_BASED
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
expect(result).toEqual([]);
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
it('Should return multiple period typeIds for NFL game in 3rd quarter', () => {
|
|
1137
|
+
const typeIds = [10021, 10022, 10023, 10024, 10031, 10032, 10051];
|
|
1138
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1139
|
+
MockNFLLiveThirdQuarter,
|
|
1140
|
+
typeIds,
|
|
1141
|
+
SportPeriodType.QUARTERS_BASED
|
|
1142
|
+
);
|
|
1143
|
+
|
|
1144
|
+
// Periods 1 and 2 are complete (period 2 also completes 1st half = 10051)
|
|
1145
|
+
expect(result).toEqual([10021, 10022, 10031, 10032, 10051]);
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
it('Should handle all 9 periods for completed MLB game', () => {
|
|
1149
|
+
const typeIds = [10021, 10022, 10023, 10024, 10025, 10026, 10027, 10028, 10029];
|
|
1150
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1151
|
+
MockMLBCompletedEvent,
|
|
1152
|
+
typeIds,
|
|
1153
|
+
SportPeriodType.INNINGS_BASED
|
|
1154
|
+
);
|
|
1155
|
+
|
|
1156
|
+
expect(result).toEqual(typeIds); // All 9 innings complete
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
it('Should return empty array when no periods complete', () => {
|
|
1160
|
+
const typeIds = [10021, 10022, 10031];
|
|
1161
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1162
|
+
MockSoccerLiveFirstHalf,
|
|
1163
|
+
typeIds,
|
|
1164
|
+
SportPeriodType.HALVES_BASED
|
|
1165
|
+
);
|
|
1166
|
+
|
|
1167
|
+
expect(result).toEqual([]);
|
|
1168
|
+
});
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
describe('Multiple typeIds boolean array checks', () => {
|
|
1172
|
+
it('Should return boolean array for live soccer in 2nd half', () => {
|
|
1173
|
+
const typeIds = [10021, 10022, 10031, 10001];
|
|
1174
|
+
const result = canResolveMultipleTypeIdsForEvent(
|
|
1175
|
+
MockSoccerLiveSecondHalf,
|
|
1176
|
+
typeIds,
|
|
1177
|
+
SportPeriodType.HALVES_BASED
|
|
1178
|
+
);
|
|
1179
|
+
|
|
1180
|
+
expect(result).toEqual([true, false, true, false]); // Period 1 typeIds are true, period 2 and full game are false
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
it('Should return false for full game typeIds during live game', () => {
|
|
1184
|
+
const typeIds = [10021, 10001, 10002, 10003];
|
|
1185
|
+
const result = canResolveMultipleTypeIdsForEvent(
|
|
1186
|
+
MockNFLLiveThirdQuarter,
|
|
1187
|
+
typeIds,
|
|
1188
|
+
SportPeriodType.QUARTERS_BASED
|
|
1189
|
+
);
|
|
1190
|
+
|
|
1191
|
+
expect(result).toEqual([true, false, false, false]); // Only period 1 is true
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
it('Should return all true for completed game', () => {
|
|
1195
|
+
const typeIds = [10021, 10022, 10001, 10002];
|
|
1196
|
+
const result = canResolveMultipleTypeIdsForEvent(
|
|
1197
|
+
MockSoccerCompletedEvent,
|
|
1198
|
+
typeIds,
|
|
1199
|
+
SportPeriodType.HALVES_BASED
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
expect(result).toEqual([true, true, true, true]); // All complete
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
it('Should return all false when no typeIds are resolvable', () => {
|
|
1206
|
+
const typeIds = [10022, 10023, 10024];
|
|
1207
|
+
const result = canResolveMultipleTypeIdsForEvent(
|
|
1208
|
+
MockSoccerLiveSecondHalf,
|
|
1209
|
+
typeIds,
|
|
1210
|
+
SportPeriodType.HALVES_BASED
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
expect(result).toEqual([false, false, false]);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
it('Should return mixed booleans for NFL game in 3rd quarter', () => {
|
|
1217
|
+
const typeIds = [10021, 10022, 10023, 10024, 10031, 10032, 10051];
|
|
1218
|
+
const result = canResolveMultipleTypeIdsForEvent(
|
|
1219
|
+
MockNFLLiveThirdQuarter,
|
|
1220
|
+
typeIds,
|
|
1221
|
+
SportPeriodType.QUARTERS_BASED
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
// Periods 1 and 2 are complete (period 2 also completes 1st half = 10051)
|
|
1225
|
+
expect(result).toEqual([true, true, false, false, true, true, true]);
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
it('Should handle all 9 periods for completed MLB game', () => {
|
|
1229
|
+
const typeIds = [10021, 10022, 10023, 10024, 10025, 10026, 10027, 10028, 10029];
|
|
1230
|
+
const result = canResolveMultipleTypeIdsForEvent(
|
|
1231
|
+
MockMLBCompletedEvent,
|
|
1232
|
+
typeIds,
|
|
1233
|
+
SportPeriodType.INNINGS_BASED
|
|
1234
|
+
);
|
|
1235
|
+
|
|
1236
|
+
expect(result).toEqual([true, true, true, true, true, true, true, true, true]); // All 9 innings complete
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
it('Should return all false when no periods complete', () => {
|
|
1240
|
+
const typeIds = [10021, 10022, 10031];
|
|
1241
|
+
const result = canResolveMultipleTypeIdsForEvent(
|
|
1242
|
+
MockSoccerLiveFirstHalf,
|
|
1243
|
+
typeIds,
|
|
1244
|
+
SportPeriodType.HALVES_BASED
|
|
1245
|
+
);
|
|
1246
|
+
|
|
1247
|
+
expect(result).toEqual([false, false, false]);
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
it('Should work with numeric sport type parameter', () => {
|
|
1251
|
+
const typeIds = [10021, 10022];
|
|
1252
|
+
const result = canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, 0); // 0 = HALVES_BASED
|
|
1253
|
+
|
|
1254
|
+
expect(result).toEqual([true, false]);
|
|
1255
|
+
});
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
describe('Edge cases', () => {
|
|
1259
|
+
it('Should handle event with no completed periods', () => {
|
|
1260
|
+
const result = canResolveMarketsForEvent(
|
|
1261
|
+
MockSoccerLiveFirstHalfInProgress,
|
|
1262
|
+
10021,
|
|
1263
|
+
SportPeriodType.HALVES_BASED
|
|
1264
|
+
);
|
|
1265
|
+
expect(result).toBe(false);
|
|
1266
|
+
});
|
|
1267
|
+
|
|
1268
|
+
it('Should respect full game typeIds list', () => {
|
|
1269
|
+
// Test all full game typeIds
|
|
1270
|
+
const fullGameTypeIds = [0, 10001, 10002, 10003, 10004, 10010, 10011, 10012];
|
|
1271
|
+
|
|
1272
|
+
fullGameTypeIds.forEach((typeId) => {
|
|
1273
|
+
const result = canResolveMarketsForEvent(
|
|
1274
|
+
MockNFLLiveThirdQuarter,
|
|
1275
|
+
typeId,
|
|
1276
|
+
SportPeriodType.QUARTERS_BASED
|
|
1277
|
+
);
|
|
1278
|
+
expect(result).toBe(false);
|
|
1279
|
+
});
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
it('Should work with sport type parameter for single typeId', () => {
|
|
1283
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10021, SportPeriodType.HALVES_BASED);
|
|
1284
|
+
expect(result).toBe(true);
|
|
1285
|
+
});
|
|
1286
|
+
|
|
1287
|
+
it('Should work with sport type parameter for batch typeIds', () => {
|
|
1288
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1289
|
+
MockSoccerLiveSecondHalf,
|
|
1290
|
+
[10021, 10022],
|
|
1291
|
+
SportPeriodType.HALVES_BASED
|
|
1292
|
+
);
|
|
1293
|
+
expect(result).toEqual([10021]);
|
|
1294
|
+
});
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
describe('Real overtime NFL game (Rams vs 49ers)', () => {
|
|
1298
|
+
it('Should detect all 5 periods complete including overtime', () => {
|
|
1299
|
+
const result = detectCompletedPeriods(MockNFLCompletedWithOvertime);
|
|
1300
|
+
|
|
1301
|
+
expect(result).not.toBeNull();
|
|
1302
|
+
expect(result?.completedPeriods).toEqual([1, 2, 3, 4, 5]);
|
|
1303
|
+
expect(result?.readyForResolution).toBe(true);
|
|
1304
|
+
expect(result?.periodScores['period5']).toEqual({ home: 0.0, away: 3.0 });
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
it('Should resolve all quarter typeIds (10021-10024) for completed overtime game', () => {
|
|
1308
|
+
expect(
|
|
1309
|
+
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10021, SportPeriodType.QUARTERS_BASED)
|
|
1310
|
+
).toBe(true);
|
|
1311
|
+
expect(
|
|
1312
|
+
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10022, SportPeriodType.QUARTERS_BASED)
|
|
1313
|
+
).toBe(true);
|
|
1314
|
+
expect(
|
|
1315
|
+
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10023, SportPeriodType.QUARTERS_BASED)
|
|
1316
|
+
).toBe(true);
|
|
1317
|
+
expect(
|
|
1318
|
+
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10024, SportPeriodType.QUARTERS_BASED)
|
|
1319
|
+
).toBe(true);
|
|
1320
|
+
});
|
|
1321
|
+
|
|
1322
|
+
it('Should NOT resolve overtime period typeId (10025) - overtime has no specific markets', () => {
|
|
1323
|
+
const result = canResolveMarketsForEvent(
|
|
1324
|
+
MockNFLCompletedWithOvertime,
|
|
1325
|
+
10025,
|
|
1326
|
+
SportPeriodType.QUARTERS_BASED
|
|
1327
|
+
);
|
|
1328
|
+
expect(result).toBe(false);
|
|
1329
|
+
});
|
|
1330
|
+
|
|
1331
|
+
it('Should resolve full game typeIds for completed overtime game', () => {
|
|
1332
|
+
expect(
|
|
1333
|
+
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10001, SportPeriodType.QUARTERS_BASED)
|
|
1334
|
+
).toBe(true);
|
|
1335
|
+
expect(
|
|
1336
|
+
canResolveMarketsForEvent(MockNFLCompletedWithOvertime, 10002, SportPeriodType.QUARTERS_BASED)
|
|
1337
|
+
).toBe(true);
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
it('Should return all resolvable typeIds excluding overtime in batch check', () => {
|
|
1341
|
+
const typeIds = [10021, 10022, 10023, 10024, 10025, 10001];
|
|
1342
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1343
|
+
MockNFLCompletedWithOvertime,
|
|
1344
|
+
typeIds,
|
|
1345
|
+
SportPeriodType.QUARTERS_BASED
|
|
1346
|
+
);
|
|
1347
|
+
|
|
1348
|
+
// 10025 (overtime) should not be included as period 5 has no specific markets
|
|
1349
|
+
expect(result).toEqual([10021, 10022, 10023, 10024, 10001]);
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
it('Should return false for 8th period typeId (period did not occur)', () => {
|
|
1353
|
+
const result = canResolveMarketsForEvent(
|
|
1354
|
+
MockNFLCompletedWithOvertime,
|
|
1355
|
+
10028,
|
|
1356
|
+
SportPeriodType.QUARTERS_BASED
|
|
1357
|
+
);
|
|
1358
|
+
expect(result).toBe(false);
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
it('Should return false for 9th period typeId (period did not occur)', () => {
|
|
1362
|
+
const result = canResolveMarketsForEvent(
|
|
1363
|
+
MockNFLCompletedWithOvertime,
|
|
1364
|
+
10029,
|
|
1365
|
+
SportPeriodType.QUARTERS_BASED
|
|
1366
|
+
);
|
|
1367
|
+
expect(result).toBe(false);
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
it('Should not include non-existent periods in batch check', () => {
|
|
1371
|
+
const typeIds = [10021, 10022, 10023, 10024, 10025, 10028, 10029];
|
|
1372
|
+
const result = filterMarketsThatCanBeResolved(
|
|
1373
|
+
MockNFLCompletedWithOvertime,
|
|
1374
|
+
typeIds,
|
|
1375
|
+
SportPeriodType.QUARTERS_BASED
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
// Periods 1-4 are resolvable; period 5 (overtime) has no markets; periods 8-9 did not occur
|
|
1379
|
+
expect(result).toEqual([10021, 10022, 10023, 10024]);
|
|
1380
|
+
});
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
describe('Sport-type-specific resolution for typeId 10051 (1st half)', () => {
|
|
1384
|
+
it('Soccer (HALVES_BASED): Should resolve typeId 10051 after period 1', () => {
|
|
1385
|
+
// Soccer: Period 1 = 1st half
|
|
1386
|
+
const result = canResolveMarketsForEvent(MockSoccerLiveSecondHalf, 10051, SportPeriodType.HALVES_BASED);
|
|
1387
|
+
expect(result).toBe(true);
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
it('NFL (QUARTERS_BASED): Should resolve typeId 10051 after period 2', () => {
|
|
1391
|
+
// NFL: Period 2 completes 1st half (quarters 1+2)
|
|
1392
|
+
const result = canResolveMarketsForEvent(
|
|
1393
|
+
MockNFLLiveThirdQuarter,
|
|
1394
|
+
10051,
|
|
1395
|
+
SportPeriodType.QUARTERS_BASED
|
|
1396
|
+
);
|
|
1397
|
+
expect(result).toBe(true);
|
|
1398
|
+
});
|
|
1399
|
+
|
|
1400
|
+
it('NFL (QUARTERS_BASED): Should NOT resolve typeId 10051 after only period 1', () => {
|
|
1401
|
+
// Create mock with only period 1 complete
|
|
1402
|
+
const mockNFLFirstQuarter = {
|
|
1403
|
+
...MockNFLLiveThirdQuarter,
|
|
1404
|
+
scores: {
|
|
1405
|
+
home: { total: 7.0, periods: { period_1: 7.0 } },
|
|
1406
|
+
away: { total: 3.0, periods: { period_1: 3.0 } },
|
|
1407
|
+
},
|
|
1408
|
+
in_play: { period: '2', clock: '5:00' },
|
|
1409
|
+
};
|
|
1410
|
+
|
|
1411
|
+
const result = canResolveMarketsForEvent(mockNFLFirstQuarter, 10051, SportPeriodType.QUARTERS_BASED);
|
|
1412
|
+
expect(result).toBe(false);
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
it('MLB (INNINGS_BASED): Should resolve typeId 10051 after period 5', () => {
|
|
1416
|
+
// MLB: Period 5 completes 1st half (innings 1-5)
|
|
1417
|
+
const result = canResolveMarketsForEvent(MockMLBLiveSixthInning, 10051, SportPeriodType.INNINGS_BASED);
|
|
1418
|
+
expect(result).toBe(true);
|
|
1419
|
+
});
|
|
1420
|
+
|
|
1421
|
+
it('MLB (INNINGS_BASED): Should NOT resolve typeId 10051 after only period 4', () => {
|
|
1422
|
+
// Create mock with only period 1-4 complete
|
|
1423
|
+
const mockMLBFourthInning = {
|
|
1424
|
+
...MockMLBLiveSixthInning,
|
|
1425
|
+
scores: {
|
|
1426
|
+
home: {
|
|
1427
|
+
total: 3.0,
|
|
1428
|
+
periods: {
|
|
1429
|
+
period_1: 0.0,
|
|
1430
|
+
period_2: 2.0,
|
|
1431
|
+
period_3: 1.0,
|
|
1432
|
+
period_4: 0.0,
|
|
1433
|
+
},
|
|
1434
|
+
},
|
|
1435
|
+
away: {
|
|
1436
|
+
total: 1.0,
|
|
1437
|
+
periods: {
|
|
1438
|
+
period_1: 1.0,
|
|
1439
|
+
period_2: 0.0,
|
|
1440
|
+
period_3: 0.0,
|
|
1441
|
+
period_4: 0.0,
|
|
1442
|
+
},
|
|
1443
|
+
},
|
|
1444
|
+
},
|
|
1445
|
+
in_play: { period: '5', clock: null },
|
|
1446
|
+
};
|
|
1447
|
+
|
|
1448
|
+
const result = canResolveMarketsForEvent(mockMLBFourthInning, 10051, SportPeriodType.INNINGS_BASED);
|
|
1449
|
+
expect(result).toBe(false);
|
|
1450
|
+
});
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
describe('Sport-type-specific resolution for typeId 10052 (2nd half)', () => {
|
|
1454
|
+
it('Soccer (HALVES_BASED): Should resolve typeId 10052 after period 2', () => {
|
|
1455
|
+
const result = canResolveMarketsForEvent(MockSoccerCompletedEvent, 10052, SportPeriodType.HALVES_BASED);
|
|
1456
|
+
expect(result).toBe(true);
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
it('NFL (QUARTERS_BASED): Should resolve typeId 10052 after period 4', () => {
|
|
1460
|
+
const result = canResolveMarketsForEvent(MockNFLCompletedEvent, 10052, SportPeriodType.QUARTERS_BASED);
|
|
1461
|
+
expect(result).toBe(true);
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
it('MLB (INNINGS_BASED): Should resolve typeId 10052 after period 9', () => {
|
|
1465
|
+
const result = canResolveMarketsForEvent(MockMLBCompletedEvent, 10052, SportPeriodType.INNINGS_BASED);
|
|
1466
|
+
expect(result).toBe(true);
|
|
1467
|
+
});
|
|
1468
|
+
});
|
|
1469
|
+
|
|
1470
|
+
describe('Error handling for invalid sport type numbers', () => {
|
|
1471
|
+
it('Should throw error for invalid sport type number when using numeric parameter', () => {
|
|
1472
|
+
const typeIds = [10021, 10022];
|
|
1473
|
+
|
|
1474
|
+
// Invalid sport type number (must be 0-3)
|
|
1475
|
+
expect(() => {
|
|
1476
|
+
canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, 99);
|
|
1477
|
+
}).toThrow('Invalid sport type number: 99. Must be 0 (halves), 1 (quarters), 2 (innings), or 3 (period).');
|
|
1478
|
+
});
|
|
1479
|
+
|
|
1480
|
+
it('Should throw error for negative sport type number', () => {
|
|
1481
|
+
const typeIds = [10021];
|
|
1482
|
+
|
|
1483
|
+
expect(() => {
|
|
1484
|
+
canResolveMultipleTypeIdsForEvent(MockSoccerLiveSecondHalf, typeIds, -1);
|
|
1485
|
+
}).toThrow('Invalid sport type number: -1. Must be 0 (halves), 1 (quarters), 2 (innings), or 3 (period).');
|
|
1486
|
+
});
|
|
1487
|
+
});
|
|
1488
|
+
});
|
|
1489
|
+
});
|