Change mock db.query.participants to db.query.seasonParticipants in test
files to match the schema rename from commit 66145a9. This fixes
"Cannot read properties of undefined (reading 'findFirst'/'findMany')"
errors that occurred when production code queries db.query.seasonParticipants
but test mocks only defined the old participants key.
Files updated:
- app/services/simulations/__tests__/world-cup-simulator.test.ts
- app/routes/api/__tests__/draft.force-manual-pick.test.ts
- app/routes/api/__tests__/draft.force-manual-pick.timer-mode.test.ts
- app/routes/api/__tests__/draft.make-pick.timer-mode.test.ts
- server/__tests__/timer-autodraft.test.ts
- app/models/__tests__/team-score-events.test.ts
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
342 lines
14 KiB
TypeScript
342 lines
14 KiB
TypeScript
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-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,
|
|
});
|
|
});
|
|
});
|
|
});
|