import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock dependencies before imports vi.mock('drizzle-orm/postgres-js', () => ({ drizzle: vi.fn(() => mockDb), })); vi.mock('postgres', () => ({ default: vi.fn(() => ({})), })); vi.mock('../socket', () => ({ getSocketIO: vi.fn(() => mockSocketIO), })); let mockDb: any; let mockSocketIO: any; // Setup mocks beforeEach(() => { mockSocketIO = { to: vi.fn().mockReturnThis(), emit: vi.fn(), }; mockDb = { query: { seasons: { findMany: vi.fn(), findFirst: vi.fn() }, draftSlots: { findMany: vi.fn() }, draftTimers: { findFirst: vi.fn() }, autodraftSettings: { findFirst: vi.fn() }, draftPicks: { findMany: vi.fn(), findFirst: vi.fn() }, draftQueue: { findMany: vi.fn() }, seasonParticipants: { findMany: vi.fn() }, seasonTemplateSports: { findMany: vi.fn() }, }, update: vi.fn().mockReturnThis(), set: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), returning: vi.fn(), insert: vi.fn().mockReturnThis(), values: vi.fn().mockReturnThis(), delete: vi.fn().mockReturnThis(), select: vi.fn().mockReturnThis(), from: vi.fn().mockReturnThis(), innerJoin: vi.fn().mockReturnThis(), orderBy: vi.fn().mockReturnThis(), limit: vi.fn().mockReturnThis(), }; }); describe('Timer Autodraft Integration', () => { describe('Autodraft Settings Check', () => { it('should check autodraft settings when timer expires', async () => { const seasonId = 'season-123'; const teamId = 'team-456'; mockDb.query.seasons.findMany.mockResolvedValue([{ id: seasonId, status: 'draft', draftPaused: false, currentPickNumber: 1, draftInitialTime: 120, draftIncrementTime: 30, draftRounds: 10, }]); mockDb.query.draftSlots.findMany.mockResolvedValue([ { id: 'slot-1', teamId, draftOrder: 1 }, { id: 'slot-2', teamId: 'team-2', draftOrder: 2 }, ]); mockDb.query.draftTimers.findFirst.mockResolvedValue({ id: 'timer-1', seasonId, teamId, timeRemaining: 0 }); mockDb.query.autodraftSettings.findFirst.mockResolvedValue({ id: 'settings-1', seasonId, teamId, isEnabled: true, mode: 'next_pick', queueOnly: false, }); expect(mockDb.query.autodraftSettings.findFirst).toBeDefined(); }); it('should use regular auto-pick when autodraft is disabled', async () => { const seasonId = 'season-123'; const teamId = 'team-456'; mockDb.query.autodraftSettings.findFirst.mockResolvedValue({ id: 'settings-1', seasonId, teamId, isEnabled: false, mode: 'next_pick', queueOnly: false, }); const settings = await mockDb.query.autodraftSettings.findFirst(); expect(settings.isEnabled).toBe(false); }); it('should handle missing autodraft settings', async () => { mockDb.query.autodraftSettings.findFirst.mockResolvedValue(null); const settings = await mockDb.query.autodraftSettings.findFirst(); expect(settings).toBeNull(); }); }); describe('Autodraft Mode Handling', () => { it('should disable autodraft after pick when mode is next_pick', async () => { const seasonId = 'season-123'; const teamId = 'team-456'; const autodraftSettings = { id: 'settings-1', seasonId, teamId, isEnabled: true, mode: 'next_pick', queueOnly: false }; mockDb.returning.mockResolvedValue([{ ...autodraftSettings, isEnabled: false, updatedAt: new Date() }]); if (autodraftSettings.isEnabled && autodraftSettings.mode === 'next_pick') { await mockDb.update(); await mockDb.set({ isEnabled: false, updatedAt: new Date() }); await mockDb.where(); const result = await mockDb.returning(); expect(result[0].isEnabled).toBe(false); } }); it('should keep autodraft enabled when mode is while_on (All Picks)', async () => { const autodraftSettings = { isEnabled: true, mode: 'while_on', queueOnly: false }; if (autodraftSettings.mode === 'while_on') { expect(autodraftSettings.isEnabled).toBe(true); } }); }); describe('Autodraft Pick Logic', () => { it('should pick from queue first when autodraft is enabled', async () => { mockDb.query.draftQueue.findMany.mockResolvedValue([ { id: 'queue-1', participantId: 'participant-1', queuePosition: 1 }, { id: 'queue-2', participantId: 'participant-2', queuePosition: 2 }, ]); mockDb.query.draftPicks.findMany.mockResolvedValue([]); const queueItems = await mockDb.query.draftQueue.findMany(); const draftedPicks = await mockDb.query.draftPicks.findMany(); const draftedParticipantIds = draftedPicks.map((p: any) => p.participantId); const availableQueueItem = queueItems.find((item: any) => !draftedParticipantIds.includes(item.participantId)); expect(availableQueueItem).toBeDefined(); expect(availableQueueItem.participantId).toBe('participant-1'); }); it('should skip drafted participants in queue and try next', async () => { mockDb.query.draftQueue.findMany.mockResolvedValue([ { id: 'queue-1', participantId: 'participant-1', queuePosition: 1 }, { id: 'queue-2', participantId: 'participant-2', queuePosition: 2 }, ]); mockDb.query.draftPicks.findMany.mockResolvedValue([{ id: 'pick-1', participantId: 'participant-1' }]); const queueItems = await mockDb.query.draftQueue.findMany(); const draftedPicks = await mockDb.query.draftPicks.findMany(); const draftedParticipantIds = draftedPicks.map((p: any) => p.participantId); const availableQueueItem = queueItems.find((item: any) => !draftedParticipantIds.includes(item.participantId)); expect(availableQueueItem.participantId).toBe('participant-2'); }); it('should fall back to highest EV when queue is empty and queueOnly is OFF', async () => { mockDb.query.draftQueue.findMany.mockResolvedValue([]); mockDb.query.seasonParticipants.findMany.mockResolvedValue([ { id: 'participant-high-ev', name: 'High EV Participant', expectedValue: 1000 }, ]); const queueItems = await mockDb.query.draftQueue.findMany(); expect(queueItems.length).toBe(0); const queueOnly = false; const availableParticipants = await mockDb.query.seasonParticipants.findMany(); const selectedParticipant = queueOnly ? null : availableParticipants[0]; expect(selectedParticipant).not.toBeNull(); expect(selectedParticipant?.expectedValue).toBe(1000); }); }); describe('Queue-Only Constraint (AC2 & AC3)', () => { it('should NOT fall back to highest EV when queueOnly is ON and queue is empty', async () => { mockDb.query.draftQueue.findMany.mockResolvedValue([]); const queueItems = await mockDb.query.draftQueue.findMany(); expect(queueItems.length).toBe(0); const queueOnly = true; const selectedParticipant = queueOnly ? null : { id: 'participant-high-ev' }; expect(selectedParticipant).toBeNull(); }); it('should disable autodraft when queueOnly is ON and queue becomes empty (AC3)', async () => { const seasonId = 'season-123'; const teamId = 'team-456'; const autodraftSettings = { id: 'settings-1', seasonId, teamId, isEnabled: true, mode: 'next_pick', queueOnly: true }; mockDb.returning.mockResolvedValue([{ ...autodraftSettings, isEnabled: false, updatedAt: new Date() }]); const participantId = null; // empty queue + queueOnly if (!participantId && autodraftSettings.queueOnly) { await mockDb.update(); await mockDb.set({ isEnabled: false, updatedAt: new Date() }); await mockDb.where(); const result = await mockDb.returning(); expect(result[0].isEnabled).toBe(false); mockSocketIO.to(`draft-${seasonId}`).emit('autodraft-updated', { teamId, isEnabled: false, mode: autodraftSettings.mode, queueOnly: autodraftSettings.queueOnly, }); expect(mockSocketIO.emit).toHaveBeenCalledWith('autodraft-updated', { teamId, isEnabled: false, mode: 'next_pick', queueOnly: true, }); } }); it('should skip drafted queue items and continue to next available item', async () => { const queue = [ { id: 'queue-1', participantId: 'participant-1', queuePosition: 1 }, { id: 'queue-2', participantId: 'participant-2', queuePosition: 2 }, ]; const drafted = new Set(['participant-1']); const availableQueueItem = queue.find((item) => !drafted.has(item.participantId)); expect(availableQueueItem?.participantId).toBe('participant-2'); }); it('should auto-disable when all queue items are drafted and queueOnly is ON', async () => { const queue = [ { id: 'queue-1', participantId: 'participant-1', queuePosition: 1 }, { id: 'queue-2', participantId: 'participant-2', queuePosition: 2 }, ]; const drafted = new Set(['participant-1', 'participant-2']); const queueOnly = true; const availableQueueItem = queue.find((item) => !drafted.has(item.participantId)); expect(availableQueueItem).toBeUndefined(); const selectedParticipant = queueOnly ? null : { id: 'fallback-ev' }; expect(selectedParticipant).toBeNull(); }); }); describe('Three-State Autodraft (AC1)', () => { it('should map Off state to isEnabled=false', () => { const offState = { isEnabled: false, mode: 'next_pick', queueOnly: false }; expect(offState.isEnabled).toBe(false); }); it('should map Next Pick state to isEnabled=true, mode=next_pick', () => { const nextPickState = { isEnabled: true, mode: 'next_pick', queueOnly: false }; expect(nextPickState.isEnabled).toBe(true); expect(nextPickState.mode).toBe('next_pick'); }); it('should map All Picks state to isEnabled=true, mode=while_on', () => { const allPicksState = { isEnabled: true, mode: 'while_on', queueOnly: false }; expect(allPicksState.isEnabled).toBe(true); expect(allPicksState.mode).toBe('while_on'); }); it('should transition Off → Next Pick correctly', () => { const initial = { isEnabled: false, mode: 'next_pick' }; const updated = { isEnabled: true, mode: 'next_pick' }; expect(updated.isEnabled).not.toBe(initial.isEnabled); expect(updated.mode).toBe('next_pick'); }); it('should transition Next Pick → All Picks correctly', () => { const nextPick = { isEnabled: true, mode: 'next_pick' }; const allPicks = { ...nextPick, mode: 'while_on' }; expect(allPicks.mode).toBe('while_on'); expect(allPicks.isEnabled).toBe(true); }); it('should auto-disable if queue is empty and queueOnly is ON (AC3)', () => { const settings = { isEnabled: true, mode: 'next_pick', queueOnly: true }; const queue: any[] = []; const shouldAutoDisable = settings.queueOnly && queue.length === 0; const finalSettings = shouldAutoDisable ? { ...settings, isEnabled: false } : settings; expect(finalSettings.isEnabled).toBe(false); }); it('should NOT auto-disable if queue is empty and queueOnly is OFF', () => { const settings = { isEnabled: true, mode: 'next_pick', queueOnly: false }; const queue: any[] = []; const shouldAutoDisable = settings.queueOnly && queue.length === 0; const finalSettings = shouldAutoDisable ? { ...settings, isEnabled: false } : settings; expect(finalSettings.isEnabled).toBe(true); }); }); describe('Socket Event Emission', () => { it('should emit autodraft-updated with queueOnly when disabling next_pick mode', async () => { const seasonId = 'season-123'; const teamId = 'team-456'; mockSocketIO.to(`draft-${seasonId}`).emit('autodraft-updated', { teamId, isEnabled: false, mode: 'next_pick', queueOnly: false, }); expect(mockSocketIO.emit).toHaveBeenCalledWith('autodraft-updated', { teamId, isEnabled: false, mode: 'next_pick', queueOnly: false, }); }); it('should emit pick-made event after autodraft pick', async () => { const seasonId = 'season-123'; const pickData = { pick: { id: 'pick-1', participantId: 'participant-1', pickNumber: 1 }, nextPickNumber: 2, isDraftComplete: false }; mockSocketIO.to(`draft-${seasonId}`).emit('pick-made', pickData); expect(mockSocketIO.emit).toHaveBeenCalledWith('pick-made', pickData); }); it('should emit autodraft-updated with queueOnly preserved on auto-disable (AC3)', async () => { const seasonId = 'season-123'; const teamId = 'team-456'; mockSocketIO.to(`draft-${seasonId}`).emit('autodraft-updated', { teamId, isEnabled: false, mode: 'next_pick', queueOnly: true, }); expect(mockSocketIO.emit).toHaveBeenCalledWith('autodraft-updated', { teamId, isEnabled: false, mode: 'next_pick', queueOnly: true, }); }); }); describe('Back-to-Back Picks', () => { it('should only autodraft first pick when mode is next_pick', async () => { const seasonId = 'season-123'; const teamId = 'team-456'; const autodraftSettings = { id: 'settings-1', seasonId, teamId, isEnabled: true, mode: 'next_pick', queueOnly: false }; if (autodraftSettings.isEnabled && autodraftSettings.mode === 'next_pick') { const updatedSettings = { ...autodraftSettings, isEnabled: false }; expect(updatedSettings.isEnabled).toBe(false); } const settingsForSecondPick = { ...autodraftSettings, isEnabled: false }; expect(settingsForSecondPick.isEnabled).toBe(false); }); it('should continue autodrafting consecutive picks when mode is while_on (All Picks)', async () => { const autodraftSettings = { isEnabled: true, mode: 'while_on', queueOnly: false }; if (autodraftSettings.mode === 'while_on') expect(autodraftSettings.isEnabled).toBe(true); expect(autodraftSettings.isEnabled).toBe(true); }); }); describe('Time Increment', () => { it('should add increment time after autodraft pick', () => { expect(0 + 30).toBe(30); }); it('should emit timer-pick-started after scheduling next pick', async () => { const seasonId = 'season-123'; const teamId = 'team-456'; mockSocketIO.to(`draft-${seasonId}`).emit('timer-pick-started', { seasonId, teamId, pickNumber: 1, expiresAt: Date.now() + 30000, timeRemaining: 30, }); expect(mockSocketIO.emit).toHaveBeenCalledWith('timer-pick-started', expect.objectContaining({ seasonId, teamId, timeRemaining: 30 }) ); }); }); });