test: add comprehensive tests for player deduplication, permissions, and user management
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* Unit Tests: Player Deduplication
|
||||
*
|
||||
* Tests the player deduplication logic for CSV uploads
|
||||
*/
|
||||
|
||||
import { describe, test, expect, vi, beforeEach } from 'vitest';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
|
||||
// Mock the prisma module
|
||||
vi.mock('@/lib/prisma', () => ({
|
||||
prisma: {
|
||||
player: {
|
||||
findFirst: vi.fn(),
|
||||
create: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Extract the findOrCreatePlayer logic for testing
|
||||
async function findOrCreatePlayer(name: string) {
|
||||
// Normalize the name for deduplication: trim whitespace and convert to lowercase
|
||||
const normalizedName = name.trim().toLowerCase();
|
||||
|
||||
// Try to find existing player with matching normalized name
|
||||
let player = await prisma.player.findFirst({
|
||||
where: { normalizedName },
|
||||
});
|
||||
|
||||
if (!player) {
|
||||
// Create a new player with the original name and normalized name
|
||||
player = await prisma.player.create({
|
||||
data: {
|
||||
name: name.trim(),
|
||||
normalizedName,
|
||||
rating: 1000,
|
||||
currentElo: 1000,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
describe('Player Deduplication', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('findOrCreatePlayer', () => {
|
||||
test('should find existing player by exact name match', async () => {
|
||||
const mockPlayer = {
|
||||
id: 1,
|
||||
name: 'Emily',
|
||||
normalizedName: 'emily',
|
||||
currentElo: 1008,
|
||||
gamesPlayed: 5,
|
||||
wins: 4,
|
||||
losses: 1,
|
||||
};
|
||||
|
||||
vi.mocked(prisma.player.findFirst).mockResolvedValue(mockPlayer);
|
||||
|
||||
const result = await findOrCreatePlayer('Emily');
|
||||
|
||||
expect(result).toEqual(mockPlayer);
|
||||
expect(prisma.player.findFirst).toHaveBeenCalledWith({
|
||||
where: { normalizedName: 'emily' },
|
||||
});
|
||||
expect(prisma.player.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should find existing player by case-insensitive match', async () => {
|
||||
const mockPlayer = {
|
||||
id: 1,
|
||||
name: 'Emily',
|
||||
normalizedName: 'emily',
|
||||
currentElo: 1008,
|
||||
gamesPlayed: 5,
|
||||
wins: 4,
|
||||
losses: 1,
|
||||
};
|
||||
|
||||
vi.mocked(prisma.player.findFirst).mockResolvedValue(mockPlayer);
|
||||
|
||||
const result = await findOrCreatePlayer('EMILY');
|
||||
|
||||
expect(result).toEqual(mockPlayer);
|
||||
expect(prisma.player.findFirst).toHaveBeenCalledWith({
|
||||
where: { normalizedName: 'emily' },
|
||||
});
|
||||
expect(prisma.player.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should trim whitespace from names', async () => {
|
||||
const mockPlayer = {
|
||||
id: 1,
|
||||
name: 'Emily',
|
||||
normalizedName: 'emily',
|
||||
currentElo: 1008,
|
||||
gamesPlayed: 5,
|
||||
wins: 4,
|
||||
losses: 1,
|
||||
};
|
||||
|
||||
vi.mocked(prisma.player.findFirst).mockResolvedValue(mockPlayer);
|
||||
|
||||
const result = await findOrCreatePlayer(' Emily ');
|
||||
|
||||
expect(result).toEqual(mockPlayer);
|
||||
expect(prisma.player.findFirst).toHaveBeenCalledWith({
|
||||
where: { normalizedName: 'emily' },
|
||||
});
|
||||
expect(prisma.player.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should create new player if not found', async () => {
|
||||
vi.mocked(prisma.player.findFirst).mockResolvedValue(null);
|
||||
|
||||
const newPlayer = {
|
||||
id: 100,
|
||||
name: 'NewPlayer',
|
||||
normalizedName: 'newplayer',
|
||||
rating: 1000,
|
||||
currentElo: 1000,
|
||||
gamesPlayed: 0,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
};
|
||||
|
||||
vi.mocked(prisma.player.create).mockResolvedValue(newPlayer);
|
||||
|
||||
const result = await findOrCreatePlayer('NewPlayer');
|
||||
|
||||
expect(result).toEqual(newPlayer);
|
||||
expect(prisma.player.findFirst).toHaveBeenCalledWith({
|
||||
where: { normalizedName: 'newplayer' },
|
||||
});
|
||||
expect(prisma.player.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
name: 'NewPlayer',
|
||||
normalizedName: 'newplayer',
|
||||
rating: 1000,
|
||||
currentElo: 1000,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle names with special characters', async () => {
|
||||
vi.mocked(prisma.player.findFirst).mockResolvedValue(null);
|
||||
|
||||
const newPlayer = {
|
||||
id: 100,
|
||||
name: 'Test-Player_123',
|
||||
normalizedName: 'test-player_123',
|
||||
rating: 1000,
|
||||
currentElo: 1000,
|
||||
gamesPlayed: 0,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
};
|
||||
|
||||
vi.mocked(prisma.player.create).mockResolvedValue(newPlayer);
|
||||
|
||||
const result = await findOrCreatePlayer('Test-Player_123');
|
||||
|
||||
expect(result).toEqual(newPlayer);
|
||||
expect(prisma.player.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
name: 'Test-Player_123',
|
||||
normalizedName: 'test-player_123',
|
||||
rating: 1000,
|
||||
currentElo: 1000,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle names with spaces', async () => {
|
||||
vi.mocked(prisma.player.findFirst).mockResolvedValue(null);
|
||||
|
||||
const newPlayer = {
|
||||
id: 100,
|
||||
name: 'Dave B',
|
||||
normalizedName: 'dave b',
|
||||
rating: 1000,
|
||||
currentElo: 1000,
|
||||
gamesPlayed: 0,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
};
|
||||
|
||||
vi.mocked(prisma.player.create).mockResolvedValue(newPlayer);
|
||||
|
||||
const result = await findOrCreatePlayer('Dave B');
|
||||
|
||||
expect(result).toEqual(newPlayer);
|
||||
expect(prisma.player.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
name: 'Dave B',
|
||||
normalizedName: 'dave b',
|
||||
rating: 1000,
|
||||
currentElo: 1000,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should deduplicate players with different cases', async () => {
|
||||
// First call with "EMILY"
|
||||
const mockPlayer = {
|
||||
id: 1,
|
||||
name: 'Emily',
|
||||
normalizedName: 'emily',
|
||||
currentElo: 1008,
|
||||
gamesPlayed: 5,
|
||||
wins: 4,
|
||||
losses: 1,
|
||||
};
|
||||
|
||||
vi.mocked(prisma.player.findFirst).mockResolvedValue(mockPlayer);
|
||||
|
||||
const result1 = await findOrCreatePlayer('EMILY');
|
||||
const result2 = await findOrCreatePlayer('Emily');
|
||||
const result3 = await findOrCreatePlayer('emily');
|
||||
|
||||
// All should return the same player
|
||||
expect(result1.id).toEqual(result2.id);
|
||||
expect(result2.id).toEqual(result3.id);
|
||||
expect(prisma.player.create).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should handle empty or whitespace-only names', async () => {
|
||||
vi.mocked(prisma.player.findFirst).mockResolvedValue(null);
|
||||
|
||||
const newPlayer = {
|
||||
id: 100,
|
||||
name: '',
|
||||
normalizedName: '',
|
||||
rating: 1000,
|
||||
currentElo: 1000,
|
||||
gamesPlayed: 0,
|
||||
wins: 0,
|
||||
losses: 0,
|
||||
};
|
||||
|
||||
vi.mocked(prisma.player.create).mockResolvedValue(newPlayer);
|
||||
|
||||
const result = await findOrCreatePlayer(' ');
|
||||
|
||||
expect(result).toEqual(newPlayer);
|
||||
expect(prisma.player.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
name: '',
|
||||
normalizedName: '',
|
||||
rating: 1000,
|
||||
currentElo: 1000,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Player Name Normalization', () => {
|
||||
test('should normalize names consistently', () => {
|
||||
const testCases = [
|
||||
{ input: 'Emily', expected: 'emily' },
|
||||
{ input: 'EMILY', expected: 'emily' },
|
||||
{ input: ' Emily ', expected: 'emily' },
|
||||
{ input: 'Dave B', expected: 'dave b' },
|
||||
{ input: 'Sara R', expected: 'sara r' },
|
||||
{ input: 'Test-Player', expected: 'test-player' },
|
||||
];
|
||||
|
||||
testCases.forEach(({ input, expected }) => {
|
||||
const normalizedName = input.trim().toLowerCase();
|
||||
expect(normalizedName).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
test('should prevent duplicate players with same normalized name', async () => {
|
||||
// Simulate checking for existing player
|
||||
const existingPlayer = {
|
||||
id: 1,
|
||||
name: 'Emily',
|
||||
normalizedName: 'emily',
|
||||
currentElo: 1008,
|
||||
gamesPlayed: 5,
|
||||
wins: 4,
|
||||
losses: 1,
|
||||
};
|
||||
|
||||
vi.mocked(prisma.player.findFirst)
|
||||
.mockResolvedValueOnce(existingPlayer) // First call for "Emily"
|
||||
.mockResolvedValueOnce(existingPlayer) // Second call for "EMILY"
|
||||
.mockResolvedValueOnce(existingPlayer); // Third call for " Emily "
|
||||
|
||||
const result1 = await findOrCreatePlayer('Emily');
|
||||
const result2 = await findOrCreatePlayer('EMILY');
|
||||
const result3 = await findOrCreatePlayer(' Emily ');
|
||||
|
||||
// All should return the same player ID
|
||||
expect(result1.id).toBe(1);
|
||||
expect(result2.id).toBe(1);
|
||||
expect(result3.id).toBe(1);
|
||||
|
||||
// No new players should be created
|
||||
expect(prisma.player.create).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user