fix: resolve schedule data staleness in production builds

- Replace window.location.reload() with router.refresh() in ScheduleGenerator, MatchEditor, RecalculateEloButton for proper Next.js cache invalidation
- Add revalidatePath() call after schedule generation in POST handler
- Add ownerId to tournament creation in cucumber tests for proper permission checks
- Assign tournament_admin role via Prisma after user creation in cucumber tests
- Fix TypeScript type annotations in hooks.ts (tournament id map)
- Update page reload to use networkidle in common-steps.ts
- Clear .next/ cache before cucumber tests in justfile
- Add .turbo to clean target
- Add comprehensive debug logging to schedule API route
- Document findings in docs/TROUBLESHOOTING_SCHEDULE_GENERATION.md
This commit is contained in:
2026-05-01 16:39:50 -07:00
parent 9353ab1edc
commit caefb0dcc0
11 changed files with 454 additions and 23 deletions
+44 -8
View File
@@ -108,16 +108,10 @@ Given('I am logged in as a player', async function () {
/**
* Precondition: I am logged in as a tournament admin
* Note: In the actual app, admin roles are assigned by club admins or via API.
* For acceptance tests, we'll use the default player role and test admin features
* as the dev site would handle them.
* For acceptance tests, we'll assign the tournament_admin role directly via Prisma.
*/
Given('I am logged in as a tournament admin', async function () {
console.log('🌍 Creating and logging in as a player (tournament admin role is assigned via UI/API)...');
// For now, use the same flow as player
// In real usage, the admin would either:
// 1. Be pre-created on the dev site
// 2. Have role assigned via API
// 3. Use the admin dashboard to manage users
console.log('🌍 Creating and logging in as a tournament admin...');
const credentials = generateTestCredentials();
world.user = credentials;
@@ -133,6 +127,34 @@ Given('I am logged in as a tournament admin', async function () {
// Wait for redirect
await world.page.waitForURL(/\/players\/\d+\/profile/, { timeout: 15000 });
// Extract user ID from the URL (e.g., /players/2147/profile)
const currentUrl = world.page.url();
const match = currentUrl.match(/\/players\/(\d+)\/profile/);
if (match) {
const playerId = match[1];
world.playerId = playerId;
// Get the user ID from the database
const prisma = await world.getPrisma();
const player = await prisma.player.findUnique({
where: { id: parseInt(playerId) },
include: { user: true }
});
if (player && player.user) {
const userId = player.user.id;
(world.user as any).id = userId;
console.log(`🌍 User ID extracted: ${userId}`);
// Assign tournament_admin role to the user
await prisma.user.update({
where: { id: userId },
data: { role: 'tournament_admin' }
});
console.log(`🌍 Assigned tournament_admin role to user: ${userId}`);
}
}
console.log(`🌍 User created: ${credentials.email}`);
});
@@ -359,11 +381,18 @@ Given('a tournament exists with {int} teams', async function (teamCount: number)
const prisma = await world.getPrisma();
const timestamp = Date.now();
// Get the current user ID for ownership
const userId = world.user?.id;
if (!userId) {
throw new Error('User ID not found. Ensure user is logged in before creating tournament.');
}
// Always create a new tournament for test isolation
const tournament = await prisma.event.create({
data: {
name: `Test Tournament ${timestamp}`,
createdAt: new Date(),
ownerId: userId, // Set the owner to the current user
},
});
@@ -409,11 +438,18 @@ Given('a tournament has a generated schedule', async function () {
const prisma = await world.getPrisma();
const timestamp = Date.now();
// Get the current user ID for ownership
const userId = world.user?.id;
if (!userId) {
throw new Error('User ID not found. Ensure user is logged in before creating tournament.');
}
// Create a tournament
const tournament = await prisma.event.create({
data: {
name: `Test Schedule Tournament ${timestamp}`,
createdAt: new Date(),
ownerId: userId, // Set the owner to the current user
},
});
@@ -104,7 +104,7 @@ When('I go back', async function () {
When('I refresh the page', async function () {
console.log('🌍 About to refresh page from URL:', world.page.url());
await world.page.reload({ waitUntil: 'domcontentloaded' });
await world.page.reload({ waitUntil: 'networkidle' });
console.log('🌍 Page refreshed, new URL:', world.page.url());
// Wait extra time for full render
await world.page.waitForTimeout(2000);
+1 -1
View File
@@ -141,7 +141,7 @@ After(async function () {
select: { id: true }
});
const tournamentIds = testTournaments.map(t => t.id);
const tournamentIds = testTournaments.map((t: { id: number }) => t.id);
if (tournamentIds.length > 0) {
// Delete bracket matchups via Prisma
+2
View File
@@ -11,6 +11,7 @@ export interface WorldState {
prisma: any; // Lazy-loaded PrismaClient
baseURL: string;
user?: {
id?: string;
email: string;
name: string;
password: string;
@@ -32,6 +33,7 @@ export class World implements WorldState {
prisma: any;
baseURL: string;
user?: {
id?: string;
email: string;
name: string;
password: string;