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>
219 lines
6.4 KiB
TypeScript
219 lines
6.4 KiB
TypeScript
/**
|
|
* Unit tests for tournament update functionality
|
|
* Tests the allowTies field is properly saved when updating tournaments
|
|
*/
|
|
|
|
import { describe, it, expect, mock, beforeEach,} from 'bun:test';
|
|
|
|
// Create mock functions at module level
|
|
const eventFindUniqueMock = mock(async () => ({}));
|
|
const eventUpdateMock = mock(async () => ({}));
|
|
const canManageTournamentMock = mock(async () => ({ allowed: true }));
|
|
const canDeleteTournamentMock = mock(async () => ({ allowed: true }));
|
|
|
|
// Mock prisma first
|
|
mock.module('@/lib/prisma', () => ({
|
|
prisma: {
|
|
event: {
|
|
findUnique: eventFindUniqueMock,
|
|
update: eventUpdateMock,
|
|
},
|
|
},
|
|
}));
|
|
|
|
// Mock the permissions module
|
|
mock.module('@/lib/permissions', () => ({
|
|
canManageTournament: canManageTournamentMock,
|
|
canDeleteTournament: canDeleteTournamentMock,
|
|
}));
|
|
|
|
// Import the route handler after mocking
|
|
import { PUT } from '@/app/api/tournaments/[id]/route';
|
|
import { prisma } from '@/lib/prisma';
|
|
|
|
describe('Tournament Update API', () => {
|
|
beforeEach(() => {
|
|
// Clear all mock history before each test
|
|
eventFindUniqueMock.mockClear();
|
|
eventUpdateMock.mockClear();
|
|
canManageTournamentMock.mockClear();
|
|
canDeleteTournamentMock.mockClear();
|
|
});
|
|
|
|
it('should update allowTies field when provided', async () => {
|
|
// Mock existing tournament
|
|
eventFindUniqueMock.mockImplementation(async () => ({
|
|
id: 1,
|
|
name: 'Test Tournament',
|
|
allowTies: false,
|
|
targetScore: 5,
|
|
eventType: 'tournament',
|
|
format: 'round_robin',
|
|
status: 'planned',
|
|
eventDate: null,
|
|
description: null,
|
|
maxParticipants: null,
|
|
ownerId: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
} as any));
|
|
|
|
// Mock successful update
|
|
eventUpdateMock.mockImplementation(async () => ({
|
|
id: 1,
|
|
name: 'Test Tournament',
|
|
allowTies: true,
|
|
targetScore: 5,
|
|
eventType: 'tournament',
|
|
format: 'round_robin',
|
|
status: 'planned',
|
|
eventDate: null,
|
|
description: null,
|
|
maxParticipants: null,
|
|
ownerId: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
} as any));
|
|
|
|
const request = new Request('http://localhost/api/tournaments/1', {
|
|
method: 'PUT',
|
|
body: JSON.stringify({
|
|
name: 'Test Tournament',
|
|
allowTies: true,
|
|
targetScore: 5,
|
|
}),
|
|
});
|
|
|
|
const params = Promise.resolve({ id: '1' });
|
|
const response = await PUT(request, { params });
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(prisma.event.update).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
data: expect.objectContaining({
|
|
allowTies: true,
|
|
}),
|
|
})
|
|
);
|
|
});
|
|
|
|
it('should NOT modify allowTies when not provided in request', async () => {
|
|
// Mock existing tournament
|
|
eventFindUniqueMock.mockImplementation(async () => ({
|
|
id: 1,
|
|
name: 'Test Tournament',
|
|
allowTies: true, // This is the current value
|
|
targetScore: 5,
|
|
eventType: 'tournament',
|
|
format: 'round_robin',
|
|
status: 'planned',
|
|
eventDate: null,
|
|
description: null,
|
|
maxParticipants: null,
|
|
ownerId: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
} as any));
|
|
|
|
// Mock successful update (allowTies should remain unchanged)
|
|
eventUpdateMock.mockImplementation(async () => ({
|
|
id: 1,
|
|
name: 'Test Tournament',
|
|
allowTies: true, // Should remain true, not reset to false
|
|
targetScore: 5,
|
|
eventType: 'tournament',
|
|
format: 'round_robin',
|
|
status: 'planned',
|
|
eventDate: null,
|
|
description: null,
|
|
maxParticipants: null,
|
|
ownerId: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
} as any));
|
|
|
|
const request = new Request('http://localhost/api/tournaments/1', {
|
|
method: 'PUT',
|
|
body: JSON.stringify({
|
|
name: 'Test Tournament',
|
|
targetScore: 5,
|
|
// allowTies not provided - should NOT be modified
|
|
}),
|
|
});
|
|
|
|
const params = Promise.resolve({ id: '1' });
|
|
const response = await PUT(request, { params });
|
|
|
|
expect(response.status).toBe(200);
|
|
// When allowTies is not provided, it should NOT be in the update data
|
|
// (it will keep its existing value in the database)
|
|
expect(eventUpdateMock.mock.calls.length).toBeGreaterThan(0);
|
|
const updateCallArgs = (eventUpdateMock.mock.calls as any[][])[0];
|
|
expect(updateCallArgs).toBeDefined();
|
|
if (updateCallArgs && updateCallArgs[0]) {
|
|
const updateData = updateCallArgs[0];
|
|
expect((updateData as any).data.allowTies).toBeUndefined();
|
|
expect((updateData as any).data.name).toBe('Test Tournament');
|
|
expect((updateData as any).data.targetScore).toBe(5);
|
|
}
|
|
});
|
|
|
|
it('should preserve allowTies value when updating other fields', async () => {
|
|
// Mock existing tournament with allowTies = true
|
|
eventFindUniqueMock.mockImplementation(async () => ({
|
|
id: 1,
|
|
name: 'Test Tournament',
|
|
allowTies: true,
|
|
targetScore: 5,
|
|
eventType: 'tournament',
|
|
format: 'round_robin',
|
|
status: 'planned',
|
|
eventDate: null,
|
|
description: null,
|
|
maxParticipants: null,
|
|
ownerId: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
} as any));
|
|
|
|
// Mock successful update
|
|
eventUpdateMock.mockImplementation(async () => ({
|
|
id: 1,
|
|
name: 'Updated Tournament Name',
|
|
allowTies: true,
|
|
targetScore: 10,
|
|
eventType: 'tournament',
|
|
format: 'round_robin',
|
|
status: 'planned',
|
|
eventDate: null,
|
|
description: null,
|
|
maxParticipants: null,
|
|
ownerId: null,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
} as any));
|
|
|
|
const request = new Request('http://localhost/api/tournaments/1', {
|
|
method: 'PUT',
|
|
body: JSON.stringify({
|
|
name: 'Updated Tournament Name',
|
|
allowTies: true,
|
|
targetScore: 10,
|
|
}),
|
|
});
|
|
|
|
const params = Promise.resolve({ id: '1' });
|
|
const response = await PUT(request, { params });
|
|
|
|
expect(response.status).toBe(200);
|
|
const updateCallArgs = (eventUpdateMock.mock.calls as any[][])[0];
|
|
expect(updateCallArgs).toBeDefined();
|
|
if (updateCallArgs && updateCallArgs[0]) {
|
|
const updateData = updateCallArgs[0];
|
|
expect((updateData as any).data.allowTies).toBe(true);
|
|
expect((updateData as any).data.name).toBe('Updated Tournament Name');
|
|
expect((updateData as any).data.targetScore).toBe(10);
|
|
}
|
|
});
|
|
});
|