brackt/server/__tests__/timer-autodraft.test.ts

548 lines
15 KiB
TypeScript
Raw Normal View History

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(),
},
participants: {
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';
// Mock active draft
mockDb.query.seasons.findMany.mockResolvedValue([
{
id: seasonId,
status: 'draft',
draftPaused: false,
currentPickNumber: 1,
draftInitialTime: 120,
draftIncrementTime: 30,
draftRounds: 10,
},
]);
// Mock draft slots
mockDb.query.draftSlots.findMany.mockResolvedValue([
{ id: 'slot-1', teamId, draftOrder: 1 },
{ id: 'slot-2', teamId: 'team-2', draftOrder: 2 },
]);
// Mock timer at 0
mockDb.query.draftTimers.findFirst.mockResolvedValue({
id: 'timer-1',
seasonId,
teamId,
timeRemaining: 0,
});
// Mock autodraft settings enabled
mockDb.query.autodraftSettings.findFirst.mockResolvedValue({
id: 'settings-1',
seasonId,
teamId,
isEnabled: true,
mode: 'next_pick',
});
// Mock no existing pick
mockDb.query.draftPicks.findFirst.mockResolvedValue(null);
// Mock queue
mockDb.query.draftQueue.findMany.mockResolvedValue([
{
id: 'queue-1',
participantId: 'participant-1',
queuePosition: 1,
},
]);
// Mock drafted picks
mockDb.query.draftPicks.findMany.mockResolvedValue([]);
// Mock insert returning created pick
mockDb.returning.mockResolvedValue([
{
id: 'pick-1',
seasonId,
teamId,
participantId: 'participant-1',
pickNumber: 1,
round: 1,
pickInRound: 1,
pickedByUserId: '',
pickedByType: 'auto',
timeUsed: 120,
createdAt: new Date(),
},
]);
// Mock select for complete pick
mockDb.limit.mockResolvedValue([
{
id: 'pick-1',
seasonId,
teamId,
participantId: 'participant-1',
pickNumber: 1,
round: 1,
pickInRound: 1,
pickedByUserId: '',
pickedByType: 'auto',
timeUsed: 120,
createdAt: new Date(),
team: { id: teamId, name: 'Test Team' },
participant: { id: 'participant-1', name: 'Test Participant' },
sport: { id: 'sport-1', name: 'Test Sport' },
},
]);
// The actual timer logic would be tested here
// This test verifies the mocks are set up correctly
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';
// Mock autodraft settings disabled
mockDb.query.autodraftSettings.findFirst.mockResolvedValue({
id: 'settings-1',
seasonId,
teamId,
isEnabled: false,
mode: 'next_pick',
});
const settings = await mockDb.query.autodraftSettings.findFirst();
expect(settings.isEnabled).toBe(false);
});
it('should handle missing autodraft settings', async () => {
const _seasonId = 'season-123';
const _teamId = 'team-456';
// Mock no autodraft settings
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',
};
// Mock update
mockDb.returning.mockResolvedValue([
{
...autodraftSettings,
isEnabled: false,
updatedAt: new Date(),
},
]);
// Simulate disabling after pick
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);
expect(mockSocketIO.to).toBeDefined();
}
});
it('should keep autodraft enabled when mode is while_on', async () => {
const _seasonId = 'season-123';
const _teamId = 'team-456';
const autodraftSettings = {
id: 'settings-1',
seasonId: _seasonId,
teamId: _teamId,
isEnabled: true,
mode: 'while_on',
};
// Should not disable when mode is while_on
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 () => {
const _seasonId = 'season-123';
const _teamId = 'team-456';
// Mock queue with available participant
mockDb.query.draftQueue.findMany.mockResolvedValue([
{
id: 'queue-1',
participantId: 'participant-1',
queuePosition: 1,
participant: {
id: 'participant-1',
name: 'Queued Participant',
},
},
{
id: 'queue-2',
participantId: 'participant-2',
queuePosition: 2,
participant: {
id: 'participant-2',
name: 'Second Queued',
},
},
]);
// Mock no drafted picks
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);
// Find first non-drafted participant from queue
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', async () => {
const _seasonId = 'season-123';
const _teamId = 'team-456';
// Mock queue
mockDb.query.draftQueue.findMany.mockResolvedValue([
{
id: 'queue-1',
participantId: 'participant-1',
queuePosition: 1,
},
{
id: 'queue-2',
participantId: 'participant-2',
queuePosition: 2,
},
]);
// Mock first participant already drafted
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);
// Should pick second participant
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 or all drafted', async () => {
const _seasonId = 'season-123';
const _teamId = 'team-456';
// Mock empty queue
mockDb.query.draftQueue.findMany.mockResolvedValue([]);
// Mock season template sports
mockDb.query.seasonTemplateSports.findMany.mockResolvedValue([
{ sportsSeasonId: 'sports-season-1' },
]);
// Mock available participants sorted by EV
mockDb.query.participants.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 availableParticipants = await mockDb.query.participants.findMany();
expect(availableParticipants[0].expectedValue).toBe(1000);
});
});
describe('Socket Event Emission', () => {
it('should emit autodraft-updated event 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',
});
expect(mockSocketIO.to).toHaveBeenCalledWith(`draft-${seasonId}`);
expect(mockSocketIO.emit).toHaveBeenCalledWith('autodraft-updated', {
teamId,
isEnabled: false,
mode: 'next_pick',
});
});
it('should emit pick-made event after autodraft pick', async () => {
const seasonId = 'season-123';
const _teamId = 'team-456';
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);
});
});
describe('Back-to-Back Picks', () => {
it('should only autodraft first pick when mode is next_pick and team has consecutive picks', async () => {
const seasonId = 'season-123';
const teamId = 'team-456';
// Mock autodraft settings with next_pick mode
const autodraftSettings = {
id: 'settings-1',
seasonId,
teamId,
isEnabled: true,
mode: 'next_pick',
};
// Simulate first pick (pick #1)
const _firstPickNumber = 1;
// After first pick, autodraft should be disabled
if (autodraftSettings.isEnabled && autodraftSettings.mode === 'next_pick') {
// Simulate disabling autodraft after first pick
const updatedSettings = {
...autodraftSettings,
isEnabled: false,
};
expect(updatedSettings.isEnabled).toBe(false);
expect(updatedSettings.mode).toBe('next_pick');
}
// Simulate second pick (pick #2) - should NOT autodraft
const _secondPickNumber = 2;
// Autodraft should now be disabled, so second pick requires manual action
const settingsForSecondPick = {
...autodraftSettings,
isEnabled: false,
};
expect(settingsForSecondPick.isEnabled).toBe(false);
// Verify that autodraft-updated event would be emitted after first pick
mockSocketIO.to(`draft-${seasonId}`).emit('autodraft-updated', {
teamId,
isEnabled: false,
mode: 'next_pick',
});
expect(mockSocketIO.emit).toHaveBeenCalledWith('autodraft-updated', {
teamId,
isEnabled: false,
mode: 'next_pick',
});
});
it('should continue autodrafting consecutive picks when mode is while_on', async () => {
const seasonId = 'season-123';
const teamId = 'team-456';
// Mock autodraft settings with while_on mode
const autodraftSettings = {
id: 'settings-1',
seasonId,
teamId,
isEnabled: true,
mode: 'while_on',
};
// Simulate first pick (pick #1)
const _firstPickNumber = 1;
// After first pick, autodraft should REMAIN enabled for while_on mode
if (autodraftSettings.mode === 'while_on') {
expect(autodraftSettings.isEnabled).toBe(true);
}
// Simulate second pick (pick #2) - SHOULD autodraft
const _secondPickNumber = 2;
// Autodraft should still be enabled for second pick
expect(autodraftSettings.isEnabled).toBe(true);
expect(autodraftSettings.mode).toBe('while_on');
});
it('should handle snake draft back-to-back picks correctly', async () => {
const seasonId = 'season-123';
const teamId = 'team-456';
const _totalTeams = 10;
// In a snake draft with 10 teams:
// Pick 10 (end of round 1) and Pick 11 (start of round 2) are back-to-back for same team
const _firstPick = 10; // Last pick of round 1
const _secondPick = 11; // First pick of round 2 (same team in snake draft)
// Mock autodraft with next_pick mode
const autodraftSettings = {
id: 'settings-1',
seasonId,
teamId,
isEnabled: true,
mode: 'next_pick',
};
// After pick 10, autodraft should be disabled
const settingsAfterFirstPick = {
...autodraftSettings,
isEnabled: false,
};
expect(settingsAfterFirstPick.isEnabled).toBe(false);
// Pick 11 should NOT autodraft because autodraft was disabled after pick 10
// This ensures the team owner has a chance to make their second pick manually
expect(settingsAfterFirstPick.mode).toBe('next_pick');
});
});
describe('Time Increment', () => {
it('should add increment time after autodraft pick', async () => {
const _seasonId = 'season-123';
const _teamId = 'team-456';
const incrementTime = 30;
const currentTime = 0;
const newTimeRemaining = currentTime + incrementTime;
expect(newTimeRemaining).toBe(30);
});
it('should emit timer-update after adding increment', async () => {
const seasonId = 'season-123';
const teamId = 'team-456';
mockSocketIO.to(`draft-${seasonId}`).emit('timer-update', {
seasonId,
teamId,
timeRemaining: 30,
currentPickNumber: 1,
});
expect(mockSocketIO.emit).toHaveBeenCalledWith('timer-update', {
seasonId,
teamId,
timeRemaining: 30,
currentPickNumber: 1,
});
});
});
});