feat: Implement tournament schedule tab and fix E2E tests #27

Merged
david merged 43 commits from feature/7-schedule-tab into main 2026-04-27 01:59:17 +00:00
2 changed files with 93 additions and 32 deletions
Showing only changes of commit d69bd7a0ba - Show all commits
+92 -29
View File
@@ -29,6 +29,46 @@ if (DB_URL.includes('_dev') || DB_URL.includes('_dev_')) {
process.exit(1); process.exit(1);
} }
// Single source of truth for test object patterns
const TEST_PATTERNS = {
players: [
'%Test%',
'%Setup%',
'%Home Test%',
'%Home Match Player%',
'%Admin User%',
'%NinePart%',
'%Nine Part%',
'%Test Player%',
'%TestUser%',
'%Cucumber%',
'%Config Admin%',
],
events: [
'%Test%',
'%Setup%',
'%Recent%',
'%Test Tournament%',
'%Cucumber%',
],
users: [
'%test%',
'%setup%',
'%cucumber%',
'%TestUser%',
]
};
// Helper to build SQL LIKE clause from patterns
function buildLikeClause(patterns) {
return patterns.map(p => `name LIKE '${p}'`).join(' OR ');
}
// Helper to build email LIKE clause
function buildEmailLikeClause(patterns) {
return patterns.map(p => `email LIKE '${p}'`).join(' OR ');
}
// Helper to run SQL and log results // Helper to run SQL and log results
function runSQL(sql, description) { function runSQL(sql, description) {
console.log(`\n🔍 ${description}...`); console.log(`\n🔍 ${description}...`);
@@ -42,13 +82,23 @@ function runSQL(sql, description) {
} }
} }
// Helper to run multi-line SQL // Helper to run SQL via file (avoids quote escaping issues)
function runMultiLineSQL(sqlLines, description) { function runSQLViaFile(sql, description) {
console.log(`\n🔍 ${description}...`); console.log(`\n🔍 ${description}...`);
const fs = require('fs');
const os = require('os');
const path = require('path');
try { try {
const sql = sqlLines.join('\n'); // Create temp file with SQL
const result = execSync(`psql "${DB_URL}" -c "${sql}"`, { encoding: 'utf8' }); const tmpFile = path.join(os.tmpdir(), `cleanup_${Date.now()}.sql`);
fs.writeFileSync(tmpFile, sql);
const result = execSync(`psql "${DB_URL}" -f "${tmpFile}"`, { encoding: 'utf8' });
console.log(result); console.log(result);
// Clean up temp file
fs.unlinkSync(tmpFile);
return true; return true;
} catch (error) { } catch (error) {
console.error(`❌ Error: ${error.message}`); console.error(`❌ Error: ${error.message}`);
@@ -56,9 +106,12 @@ function runMultiLineSQL(sqlLines, description) {
} }
} }
// Helper to count records matching pattern // Helper to count records matching any of the patterns
function countRecords(table, column, pattern) { function countRecordsByPatterns(table, patterns) {
const sql = `SELECT COUNT(*) FROM ${table} WHERE ${column} LIKE '${pattern}';`; const whereClause = table === 'users'
? buildEmailLikeClause(patterns)
: buildLikeClause(patterns);
const sql = `SELECT COUNT(*) FROM ${table} WHERE ${whereClause};`;
try { try {
const result = execSync(`psql "${DB_URL}" -c "${sql}" -t`, { encoding: 'utf8' }).trim(); const result = execSync(`psql "${DB_URL}" -c "${sql}" -t`, { encoding: 'utf8' }).trim();
return parseInt(result); return parseInt(result);
@@ -89,57 +142,67 @@ async function main() {
console.log('\n📋 Checking test records...\n'); console.log('\n📋 Checking test records...\n');
// Count test players // Count test records using centralized patterns
const testPlayerCount = countRecords('players', 'name', '%Test%') + const testPlayerCount = countRecordsByPatterns('players', TEST_PATTERNS.players);
countRecords('players', 'name', '%Setup%') +
countRecords('players', 'name', '%Home Test%') +
countRecords('players', 'name', '%Home Match Player%') +
countRecords('players', 'name', '%Admin User%');
console.log(`Test players found: ${testPlayerCount}`); console.log(`Test players found: ${testPlayerCount}`);
// Count test tournaments const testEventCount = countRecordsByPatterns('events', TEST_PATTERNS.events);
const testEventCount = countRecords('events', 'name', '%Test%') +
countRecords('events', 'name', '%Setup%') +
countRecords('events', 'name', '%Recent%');
console.log(`Test tournaments found: ${testEventCount}`); console.log(`Test tournaments found: ${testEventCount}`);
// Count test users const testUserCount = countRecordsByPatterns('users', TEST_PATTERNS.users);
const testUserCount = countRecords('users', 'email', '%test%') +
countRecords('users', 'email', '%setup%');
console.log(`Test users found: ${testUserCount}`); console.log(`Test users found: ${testUserCount}`);
console.log('\n🔄 Starting cleanup...\n'); console.log('\n🔄 Starting cleanup...\n');
// Step 1: Delete test tournaments (with matches due to cascade delete) // Step 1: Delete test tournaments (with matches due to cascade delete)
console.log('1. Deleting test tournaments (matches will be deleted via cascade)...'); console.log('1. Deleting test tournaments (matches will be deleted via cascade)...');
runSQL( const eventWhere = buildLikeClause(TEST_PATTERNS.events);
"DELETE FROM events WHERE (name LIKE '%Test%' OR name LIKE '%Setup%' OR name LIKE '%Recent%');", runSQLViaFile(
`DELETE FROM events WHERE (${eventWhere});`,
'Deleted test tournaments' 'Deleted test tournaments'
); );
// Step 2: Delete test players (all of them, since we deleted their matches) // Step 2: Delete test players (all of them, since we deleted their matches)
console.log('\n2. Deleting test players...'); console.log('\n2. Deleting test players...');
runSQL( const playerWhere = buildLikeClause(TEST_PATTERNS.players);
"DELETE FROM players WHERE (name LIKE '%Test%' OR name LIKE '%Setup%' OR name LIKE '%Home Test%' OR name LIKE '%Home Match Player%' OR name LIKE '%Admin User%');", runSQLViaFile(
`DELETE FROM players WHERE (${playerWhere});`,
'Deleted test players' 'Deleted test players'
); );
// Step 3: Delete test users (they shouldn't have player associations) // Step 3: Delete test users (they shouldn't have player associations)
console.log('\n3. Deleting test users...'); console.log('\n3. Deleting test users...');
runSQL( const userWhere = buildEmailLikeClause(TEST_PATTERNS.users);
"DELETE FROM users WHERE (email LIKE '%test%' OR email LIKE '%setup%') AND \"playerId\" IS NULL;", runSQLViaFile(
`DELETE FROM users WHERE (${userWhere}) AND "playerId" IS NULL;`,
'Deleted test users' 'Deleted test users'
); );
// Summary // Summary
console.log('\n✅ Cleanup complete!'); console.log('\n✅ Cleanup complete!');
console.log('\n📊 Remaining test records:'); console.log('\n📊 Remaining test records:');
runSQL("SELECT COUNT(*) FROM players WHERE name LIKE '%Test%' OR name LIKE '%Setup%' OR name LIKE '%Home Test%';", 'Remaining test players');
runSQL("SELECT COUNT(*) FROM events WHERE name LIKE '%Test%' OR name LIKE '%Setup%';", 'Remaining test tournaments'); const remainingPlayerWhere = buildLikeClause(TEST_PATTERNS.players);
runSQL("SELECT COUNT(*) FROM users WHERE email LIKE '%test%' OR email LIKE '%setup%';", 'Remaining test users'); runSQLViaFile(
`SELECT COUNT(*) FROM players WHERE ${remainingPlayerWhere};`,
'Remaining test players'
);
const remainingEventWhere = buildLikeClause(TEST_PATTERNS.events);
runSQLViaFile(
`SELECT COUNT(*) FROM events WHERE ${remainingEventWhere};`,
'Remaining test tournaments'
);
const remainingUserWhere = buildEmailLikeClause(TEST_PATTERNS.users);
runSQLViaFile(
`SELECT COUNT(*) FROM users WHERE ${remainingUserWhere};`,
'Remaining test users'
);
console.log('\n💡 Note: All test records deleted. Matches are automatically deleted'); console.log('\n💡 Note: All test records deleted. Matches are automatically deleted');
console.log(' via cascade delete when their parent tournament is deleted.'); console.log(' via cascade delete when their parent tournament is deleted.');
console.log('\n📝 To add new test patterns, edit TEST_PATTERNS in this script.');
}); });
} }
+1 -3
View File
@@ -1,13 +1,11 @@
import { PrismaClient } from '@prisma/client' import { PrismaClient } from '@prisma/client'
// Load .env file if it exists
require('dotenv').config()
const globalForPrisma = globalThis as unknown as { const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined prisma: PrismaClient | undefined
} }
// Detect database provider from environment (default to sqlite for local development) // Detect database provider from environment (default to sqlite for local development)
// Next.js automatically loads environment variables from .env, .env.development, .env.production
const databaseProvider = process.env.DATABASE_PROVIDER || 'sqlite' const databaseProvider = process.env.DATABASE_PROVIDER || 'sqlite'
const databaseUrl = process.env.DATABASE_URL const databaseUrl = process.env.DATABASE_URL