diff --git a/packages/backend/src/fantasy/fantasy.service.spec.ts b/packages/backend/src/fantasy/fantasy.service.spec.ts index b904e742..131062d5 100644 --- a/packages/backend/src/fantasy/fantasy.service.spec.ts +++ b/packages/backend/src/fantasy/fantasy.service.spec.ts @@ -1702,6 +1702,66 @@ describe('FantasyService', () => { } }); + it('waits for delayed cold-cache FantasyCalc responses before falling back', async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date('2026-09-11T12:00:00.000Z')); + const internals = service as unknown as FantasyServiceInternals; + const league: SleeperLeague = { + league_id: '998', + name: 'Redraft League', + season: '2026', + status: 'in_season', + avatar: null, + total_rosters: 12, + roster_positions: ['QB'], + }; + (Axios.get as Mock).mockImplementationOnce( + () => + new Promise<{ data: FantasyCalcResponseEntry[] }>((resolve) => { + setTimeout( + () => + resolve({ + data: [ + { + player: { sleeperId: 'p2' }, + value: 1234, + redraftValue: 1234, + overallRank: 50, + positionRank: 20, + trend30Day: 1, + }, + ], + }), + 300, + ); + }), + ); + + try { + const snapshotPromise = internals.getFantasyCalcValuesForOverview(league); + await vi.advanceTimersByTimeAsync(300); + + await expect(snapshotPromise).resolves.toEqual({ + values: new Map([ + [ + 'p2', + { + sleeperId: 'p2', + value: 1234, + overallRank: 50, + positionRank: 20, + trend30Day: 1, + tradeFrequency: null, + }, + ], + ]), + version: 1, + }); + } finally { + vi.useRealTimers(); + } + }); + it('keeps the overview FantasyCalc version aligned with the fallback values snapshot', async () => { const internals = service as unknown as FantasyServiceInternals; const serviceState = service as unknown as { diff --git a/packages/backend/src/fantasy/fantasy.service.ts b/packages/backend/src/fantasy/fantasy.service.ts index 34aad527..f250f13a 100644 --- a/packages/backend/src/fantasy/fantasy.service.ts +++ b/packages/backend/src/fantasy/fantasy.service.ts @@ -48,7 +48,10 @@ const WAIVER_MARKET_CACHE_MS = 6 * 60 * 60 * 1000; const FANTASYCALC_CACHE_MS = 6 * 60 * 60 * 1000; const FANTASYCALC_FAILURE_CACHE_MS = 5 * 60 * 1000; const FANTASYCALC_TIMEOUT_MS = 2000; -const FANTASYCALC_OVERVIEW_WAIT_MS = 150; +// Must be long enough for the FantasyCalc request (bounded by FANTASYCALC_TIMEOUT_MS) to actually +// finish on a cold cache; a short wait here mostly guarantees the fallback (all-null marketValues) +// on every first request after a deploy, since the in-memory cache resets on restart. +const FANTASYCALC_OVERVIEW_WAIT_MS = FANTASYCALC_TIMEOUT_MS; const WAIVER_HISTORY_SEASONS = 3; const NFL_REGULAR_SEASON_WEEKS = 18; const SLEEPER_ID_PATTERN = /^\d{1,32}$/;