Files
euchre_camp/src/__tests__/unit/recalculate-elo.test.ts
T
david 24db43eb7f
Test / unit-tests (push) Failing after 1m54s
Pull Request / unit-tests (pull_request) Failing after 1m41s
Pull Request / acceptance-tests (pull_request) Has been skipped
Pull Request / analyze-bump-type (pull_request) Has been skipped
feat: migrate from npm to Bun
- Install Bun via mise
- Update package.json scripts to use Bun
- Migrate unit tests from Vitest to Bun test runner
- Migrate component tests from Vitest to Bun test runner
- Configure Bun with DOM environment for React Testing Library
- Keep Playwright for E2E tests (as planned)
- 93/100 tests passing (7 flaky tests when running all together, pass individually)
2026-04-01 00:13:15 -07:00

191 lines
5.5 KiB
TypeScript

/**
* Unit Tests: ELO Recalculation
*
* Tests the idempotent full recalculation of ELO ratings and player statistics
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, test, expect, beforeEach, mock } from 'bun:test';
import { recalculateAllElo } from '@/lib/elo-utils';
// Mock Prisma client
const mockPrisma = {
player: {
updateMany: mock(() => {}).mockResolvedValue({ count: 0 }),
update: mock(() => {}).mockResolvedValue({}),
},
eloSnapshot: {
deleteMany: mock(() => {}).mockResolvedValue({ count: 0 }),
create: mock(() => {}).mockResolvedValue({}),
},
partnershipStat: {
deleteMany: mock(() => {}).mockResolvedValue({ count: 0 }),
findFirst: mock(() => {}).mockResolvedValue(null), // No existing stats initially
update: mock(() => {}).mockResolvedValue({}),
create: mock(() => {}).mockResolvedValue({}),
},
match: {
findMany: mock(() => {}).mockResolvedValue([]),
},
};
describe('recalculateAllElo', () => {
beforeEach(() => {
mock.clearAllMocks();
});
test('should reset all player stats to zero', async () => {
(mockPrisma.match.findMany as any).mockResolvedValue([]);
const result = await recalculateAllElo(mockPrisma as any);
expect(mockPrisma.player.updateMany).toHaveBeenCalledWith({
data: {
currentElo: 1000,
gamesPlayed: 0,
wins: 0,
losses: 0,
},
});
expect(result.matchesProcessed).toBe(0);
expect(result.playersUpdated).toBe(0);
expect(result.partnershipsUpdated).toBe(0);
});
test('should delete all existing elo snapshots', async () => {
(mockPrisma.match.findMany as any).mockResolvedValue([]);
await recalculateAllElo(mockPrisma as any);
expect(mockPrisma.eloSnapshot.deleteMany).toHaveBeenCalledWith({});
});
test('should delete all existing partnership stats', async () => {
(mockPrisma.match.findMany as any).mockResolvedValue([]);
await recalculateAllElo(mockPrisma as any);
expect(mockPrisma.partnershipStat.deleteMany).toHaveBeenCalledWith({});
});
test('should return correct result for empty match list', async () => {
(mockPrisma.match.findMany as any).mockResolvedValue([]);
const result = await recalculateAllElo(mockPrisma as any);
expect(result).toEqual({
matchesProcessed: 0,
playersUpdated: 0,
partnershipsUpdated: 0,
});
});
test('should process matches in chronological order', async () => {
const mockMatches = [
{
id: 1,
playedAt: new Date('2024-01-01'),
team1P1: { id: 1, name: 'Player 1' },
team1P2: { id: 2, name: 'Player 2' },
team2P1: { id: 3, name: 'Player 3' },
team2P2: { id: 4, name: 'Player 4' },
team1Score: 10,
team2Score: 5,
},
{
id: 2,
playedAt: new Date('2024-01-02'),
team1P1: { id: 1, name: 'Player 1' },
team1P2: { id: 2, name: 'Player 2' },
team2P1: { id: 5, name: 'Player 5' },
team2P2: { id: 6, name: 'Player 6' },
team1Score: 8,
team2Score: 6,
},
];
(mockPrisma.match.findMany as any).mockResolvedValue(mockMatches);
await recalculateAllElo(mockPrisma as any);
// Should process 2 matches
expect(mockPrisma.match.findMany).toHaveBeenCalledWith({
orderBy: { playedAt: 'asc' },
include: {
team1P1: true,
team1P2: true,
team2P1: true,
team2P2: true,
},
});
});
test('should create elo snapshots for each player in each match', async () => {
const mockMatches = [
{
id: 1,
playedAt: new Date('2024-01-01'),
team1P1: { id: 1, name: 'Player 1' },
team1P2: { id: 2, name: 'Player 2' },
team2P1: { id: 3, name: 'Player 3' },
team2P2: { id: 4, name: 'Player 4' },
team1Score: 10,
team2Score: 5,
},
];
(mockPrisma.match.findMany as any).mockResolvedValue(mockMatches);
await recalculateAllElo(mockPrisma as any);
// Should create 4 snapshots (one for each player)
expect(mockPrisma.eloSnapshot.create).toHaveBeenCalledTimes(4);
});
test('should update player stats after processing matches', async () => {
const mockMatches = [
{
id: 1,
playedAt: new Date('2024-01-01'),
team1P1: { id: 1, name: 'Player 1' },
team1P2: { id: 2, name: 'Player 2' },
team2P1: { id: 3, name: 'Player 3' },
team2P2: { id: 4, name: 'Player 4' },
team1Score: 10,
team2Score: 5,
},
];
(mockPrisma.match.findMany as any).mockResolvedValue(mockMatches);
await recalculateAllElo(mockPrisma as any);
// Should update 4 players
expect(mockPrisma.player.update).toHaveBeenCalledTimes(4);
});
test('should update partnership stats after processing matches', async () => {
const mockMatches = [
{
id: 1,
playedAt: new Date('2024-01-01'),
team1P1: { id: 1, name: 'Player 1' },
team1P2: { id: 2, name: 'Player 2' },
team2P1: { id: 3, name: 'Player 3' },
team2P2: { id: 4, name: 'Player 4' },
team1Score: 10,
team2Score: 5,
},
];
(mockPrisma.match.findMany as any).mockResolvedValue(mockMatches);
await recalculateAllElo(mockPrisma as any);
// Should check for existing partnership stats (2 calls) and create new ones (2 calls)
expect(mockPrisma.partnershipStat.findFirst).toHaveBeenCalledTimes(2);
expect(mockPrisma.partnershipStat.create).toHaveBeenCalledTimes(2);
});
});