Files
euchre_camp/src/__tests__/unit/player-profile.test.ts
T
david bb6be245b7
Release / release (push) Failing after 9s
Build CI Images / build-ci-base (push) Failing after 18s
feat: Implement tournament schedule tab and fix E2E tests (#27)
## 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>
2026-04-27 01:59:16 +00:00

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");
});
});
});