bb6be245b7
## Summary This PR implements the tournament schedule tab functionality and fixes all remaining E2E test failures. ### Changes Included 1. **Tournament Schedule Feature** - Added tournament schedule page at `/admin/tournaments/[id]/schedule` - Implemented "Generate Schedule" button functionality - Added schedule generation logic for round-robin tournaments 2. **E2E Test Fixes** - Fixed database connection issues in production builds - Improved test reliability with better error handling and debugging - Updated test infrastructure to use environment variables instead of hardcoded values 3. **CI/CD Updates** - Added E2E test job to PR workflow - Configured tests to run against development database - Moved database password to Gitea secrets 4. **Code Quality** - Removed hardcoded passwords from codebase - Improved Prisma client configuration - Enhanced authentication and navigation components ### Test Results All 16 E2E test scenarios are now passing: - Authentication tests: ✅ - Registration tests: ✅ - Tournament schedule tests: ✅ - Player schedule tests: ✅ - Admin navigation tests: ✅ ### Database Configuration - Tests run against `euchre_camp_dev` database - Production builds use environment variables for database configuration - Database password stored in Gitea secrets as `DB_PASSWORD` ### CI Pipeline The PR workflow now includes: 1. Unit tests 2. E2E tests (using production build) 3. Version bump analysis E2E tests must pass before PR can be merged. Reviewed-on: #27 Co-authored-by: David Gwilliam <dhgwilliam@gmail.com> Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
326 lines
8.7 KiB
TypeScript
326 lines
8.7 KiB
TypeScript
/**
|
|
* Unit Tests: Player Profile Enhancements
|
|
*
|
|
* Tests for player profile page enhancements including:
|
|
* - Tournaments participated in
|
|
* - Recent games
|
|
* - Partnership performance
|
|
*/
|
|
|
|
import { describe, test, expect, mock, beforeEach } from 'bun:test';
|
|
import { prisma } from '@/lib/prisma';
|
|
import type { Player, Event, Match, PartnershipStat } from '@prisma/client';
|
|
|
|
// Create mock functions at module level
|
|
const playerFindUniqueMock = mock(() => {});
|
|
const eventFindManyMock = mock(() => {});
|
|
const matchFindManyMock = mock(() => {});
|
|
const partnershipStatFindManyMock = mock(() => {});
|
|
|
|
// Mock the prisma module
|
|
mock.module('@/lib/prisma', () => ({
|
|
prisma: {
|
|
player: {
|
|
findUnique: playerFindUniqueMock,
|
|
},
|
|
event: {
|
|
findMany: eventFindManyMock,
|
|
},
|
|
match: {
|
|
findMany: matchFindManyMock,
|
|
},
|
|
partnershipStat: {
|
|
findMany: partnershipStatFindManyMock,
|
|
},
|
|
},
|
|
}));
|
|
|
|
// Helper to create mock player
|
|
const createMockPlayer = (id: number, name: string): Player => ({
|
|
id,
|
|
name,
|
|
normalizedName: name.toLowerCase(),
|
|
rating: 0,
|
|
currentElo: 1000,
|
|
gamesPlayed: 10,
|
|
wins: 6,
|
|
losses: 4,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
});
|
|
|
|
// Helper to create mock tournament
|
|
const createMockTournament = (id: number, name: string): Event => ({
|
|
id,
|
|
name,
|
|
description: null,
|
|
eventDate: new Date(),
|
|
eventType: 'tournament',
|
|
tournamentType: 'individual',
|
|
format: 'round_robin',
|
|
status: 'completed',
|
|
maxParticipants: null,
|
|
ownerId: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
event_id: null,
|
|
targetScore: null,
|
|
allowTies: false,
|
|
teamDurability: 'permanent',
|
|
partnerRotation: 'none',
|
|
allowByes: true,
|
|
teamConfiguration: null,
|
|
maxRosterChanges: null,
|
|
requireAdminVerify: false,
|
|
});
|
|
|
|
// Helper to create mock match
|
|
const createMockMatch = (
|
|
id: number,
|
|
team1P1Id: number,
|
|
team1P2Id: number,
|
|
team2P1Id: number,
|
|
team2P2Id: number,
|
|
team1Score: number,
|
|
team2Score: number,
|
|
eventId?: number
|
|
): Match => ({
|
|
id,
|
|
eventId: eventId || null,
|
|
playedAt: new Date(),
|
|
player1P1Id: team1P1Id,
|
|
player1P2Id: team1P2Id,
|
|
player2P1Id: team2P1Id,
|
|
player2P2Id: team2P2Id,
|
|
team1Score,
|
|
team2Score,
|
|
status: 'completed',
|
|
createdById: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
isCasual: false,
|
|
});
|
|
|
|
// Helper to create mock partnership stat
|
|
const createMockPartnershipStat = (
|
|
id: number,
|
|
player1Id: number,
|
|
player2Id: number,
|
|
gamesPlayed: number,
|
|
wins: number,
|
|
losses: number
|
|
): PartnershipStat => ({
|
|
id,
|
|
player1Id,
|
|
player2Id,
|
|
gamesPlayed,
|
|
wins,
|
|
losses,
|
|
totalEloChange: 0,
|
|
lastPlayed: new Date(),
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
});
|
|
|
|
describe('Player Profile Enhancements', () => {
|
|
beforeEach(() => {
|
|
// Reset mock implementations to default (no-op) before each test
|
|
playerFindUniqueMock.mockImplementation(() => undefined);
|
|
eventFindManyMock.mockImplementation(() => undefined);
|
|
matchFindManyMock.mockImplementation(() => undefined);
|
|
partnershipStatFindManyMock.mockImplementation(() => undefined);
|
|
});
|
|
|
|
describe('Tournaments Participated', () => {
|
|
test('should fetch tournaments where player participated', async () => {
|
|
const mockPlayer = createMockPlayer(1, 'Test Player');
|
|
const mockTournaments = [
|
|
createMockTournament(1, 'Tournament A'),
|
|
createMockTournament(2, 'Tournament B'),
|
|
];
|
|
|
|
playerFindUniqueMock.mockImplementation(async () => mockPlayer);
|
|
eventFindManyMock.mockImplementation(async () => mockTournaments);
|
|
|
|
// Simulate the query that would be run
|
|
const tournaments = await prisma.event.findMany({
|
|
where: {
|
|
participants: {
|
|
some: {
|
|
playerId: 1,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { eventDate: 'desc' },
|
|
take: 10,
|
|
});
|
|
|
|
expect(tournaments).toEqual(mockTournaments);
|
|
expect(tournaments.length).toBe(2);
|
|
});
|
|
|
|
test('should return empty array if player has no tournaments', async () => {
|
|
const mockPlayer = createMockPlayer(1, 'Test Player');
|
|
|
|
playerFindUniqueMock.mockImplementation(async () => mockPlayer);
|
|
eventFindManyMock.mockImplementation(async () => []);
|
|
|
|
const tournaments = await prisma.event.findMany({
|
|
where: {
|
|
participants: {
|
|
some: {
|
|
playerId: 1,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { eventDate: 'desc' },
|
|
take: 10,
|
|
});
|
|
|
|
expect(tournaments).toEqual([]);
|
|
expect(tournaments.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Recent Games', () => {
|
|
test('should fetch recent matches for a player', async () => {
|
|
const mockPlayer = createMockPlayer(1, 'Test Player');
|
|
const mockMatches = [
|
|
createMockMatch(1, 1, 2, 3, 4, 5, 3),
|
|
createMockMatch(2, 1, 3, 5, 6, 4, 4),
|
|
];
|
|
|
|
playerFindUniqueMock.mockImplementation(async () => mockPlayer);
|
|
matchFindManyMock.mockImplementation(async () => mockMatches);
|
|
|
|
// Simulate the query that would be run
|
|
const matches = await prisma.match.findMany({
|
|
where: {
|
|
OR: [
|
|
{ player1P1Id: 1 },
|
|
{ player1P2Id: 1 },
|
|
{ player2P1Id: 1 },
|
|
{ player2P2Id: 1 },
|
|
],
|
|
},
|
|
include: {
|
|
player1P1: true,
|
|
player1P2: true,
|
|
player2P1: true,
|
|
player2P2: true,
|
|
event: true,
|
|
},
|
|
orderBy: { playedAt: 'desc' },
|
|
take: 10,
|
|
});
|
|
|
|
// The mock returns the raw data without relations, so we just check the length
|
|
expect(matches.length).toBe(2);
|
|
});
|
|
|
|
test('should return empty array if player has no matches', async () => {
|
|
const mockPlayer = createMockPlayer(1, 'Test Player');
|
|
|
|
playerFindUniqueMock.mockImplementation(async () => mockPlayer);
|
|
matchFindManyMock.mockImplementation(async () => []);
|
|
|
|
const matches = await prisma.match.findMany({
|
|
where: {
|
|
OR: [
|
|
{ player1P1Id: 1 },
|
|
{ player1P2Id: 1 },
|
|
{ player2P1Id: 1 },
|
|
{ player2P2Id: 1 },
|
|
],
|
|
},
|
|
include: {
|
|
player1P1: true,
|
|
player1P2: true,
|
|
player2P1: true,
|
|
player2P2: true,
|
|
event: true,
|
|
},
|
|
orderBy: { playedAt: 'desc' },
|
|
take: 10,
|
|
});
|
|
|
|
expect(matches).toEqual([]);
|
|
expect(matches.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Partnership Performance', () => {
|
|
test('should fetch partnership stats for a player', async () => {
|
|
const mockPlayer = createMockPlayer(1, 'Test Player');
|
|
const mockPartnershipStats = [
|
|
createMockPartnershipStat(1, 1, 2, 10, 7, 3),
|
|
createMockPartnershipStat(2, 1, 3, 5, 2, 3),
|
|
];
|
|
|
|
playerFindUniqueMock.mockImplementation(async () => mockPlayer);
|
|
partnershipStatFindManyMock.mockImplementation(async () => mockPartnershipStats);
|
|
|
|
// Simulate the query that would be run
|
|
const partnershipStats = await prisma.partnershipStat.findMany({
|
|
where: {
|
|
OR: [
|
|
{ player1Id: 1 },
|
|
{ player2Id: 1 },
|
|
],
|
|
},
|
|
include: {
|
|
player1: true,
|
|
player2: true,
|
|
},
|
|
orderBy: { gamesPlayed: 'desc' },
|
|
});
|
|
|
|
// The mock returns the raw data without relations, so we just check the length
|
|
expect(partnershipStats.length).toBe(2);
|
|
});
|
|
|
|
test('should return empty array if player has no partnership data', async () => {
|
|
const mockPlayer = createMockPlayer(1, 'Test Player');
|
|
|
|
playerFindUniqueMock.mockImplementation(async () => mockPlayer);
|
|
partnershipStatFindManyMock.mockImplementation(async () => []);
|
|
|
|
const partnershipStats = await prisma.partnershipStat.findMany({
|
|
where: {
|
|
OR: [
|
|
{ player1Id: 1 },
|
|
{ player2Id: 1 },
|
|
],
|
|
},
|
|
include: {
|
|
player1: true,
|
|
player2: true,
|
|
},
|
|
orderBy: { gamesPlayed: 'desc' },
|
|
});
|
|
|
|
expect(partnershipStats).toEqual([]);
|
|
expect(partnershipStats.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('Profile Statistics', () => {
|
|
test('should calculate win rate correctly', async () => {
|
|
const mockPlayer = createMockPlayer(1, 'Test Player');
|
|
// wins: 6, gamesPlayed: 10
|
|
const winRate = (mockPlayer.wins / mockPlayer.gamesPlayed) * 100;
|
|
|
|
expect(winRate).toBe(60.0);
|
|
});
|
|
|
|
test('should handle zero games played', async () => {
|
|
const mockPlayer = { ...createMockPlayer(1, 'Test Player'), gamesPlayed: 0, wins: 0 };
|
|
const winRate = mockPlayer.gamesPlayed > 0
|
|
? ((mockPlayer.wins / mockPlayer.gamesPlayed) * 100).toFixed(1)
|
|
: "0.0";
|
|
|
|
expect(winRate).toBe("0.0");
|
|
});
|
|
});
|
|
});
|