861e14503b
## Summary - Fix `isProductionDatabase()` to allow CI database (`euchre_camp_ci`) - Add database schema reset before CI test runs - Create `global.teardown.ts` for cleanup (CI: full reset, dev/prod: selective cleanup) - Add `acceptance-tests` job to PR workflow with CI database - Create `sync-prod-to-dev.js` script for one-way prod→dev sync - Add `just` recipes: `sync-dev`, `test-prod`, `reset-ci-db` - Store credentials in `.credentials` (gitignored) with unique CI user ## Testing Verified against CI database: - Schema reset works - Migrations apply correctly - Test users created successfully - 219 tests pass (slow but working) ## Next Steps - Set `CI_DATABASE_URL` as Gitea repository variable Reviewed-on: #35 Co-authored-by: David Gwilliam <dhgwilliam@gmail.com> Co-committed-by: David Gwilliam <dhgwilliam@gmail.com>
132 lines
3.9 KiB
TypeScript
132 lines
3.9 KiB
TypeScript
/**
|
|
* Global teardown for Playwright tests
|
|
* Handles cleanup based on environment:
|
|
* - CI: Full schema reset (database can be destroyed and recreated)
|
|
* - Dev: Selective cleanup of test records (preserve real data)
|
|
* - Prod: Selective cleanup of test records (preserve real data)
|
|
*/
|
|
|
|
import { type FullConfig } from '@playwright/test';
|
|
import { PrismaClient } from '@prisma/client';
|
|
import { PrismaPg } from '@prisma/adapter-pg';
|
|
import { execSync } from 'child_process';
|
|
|
|
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%',
|
|
]
|
|
};
|
|
|
|
function isDatabase(url: string, name: string): boolean {
|
|
return url.includes(name);
|
|
}
|
|
|
|
function isProductionDatabase(): boolean {
|
|
const dbUrl = process.env.DATABASE_URL || '';
|
|
return isDatabase(dbUrl, 'euchre_camp') && !isDatabase(dbUrl, '_dev') && !isDatabase(dbUrl, '_ci');
|
|
}
|
|
|
|
function isCIDatabase(): boolean {
|
|
const dbUrl = process.env.DATABASE_URL || '';
|
|
return isDatabase(dbUrl, '_ci');
|
|
}
|
|
|
|
function buildLikeClause(patterns: string[]): string {
|
|
return patterns.map(p => `name LIKE '${p}'`).join(' OR ');
|
|
}
|
|
|
|
function buildEmailLikeClause(patterns: string[]): string {
|
|
return patterns.map(p => `email LIKE '${p}'`).join(' OR ');
|
|
}
|
|
|
|
function createPrismaClient() {
|
|
const databaseUrl = process.env.DATABASE_URL;
|
|
if (!databaseUrl) throw new Error('DATABASE_URL is required');
|
|
const adapter = new PrismaPg({ connectionString: databaseUrl });
|
|
return new PrismaClient({ adapter });
|
|
}
|
|
|
|
async function resetDatabaseSchema(prisma: PrismaClient) {
|
|
console.log('Resetting database schema...');
|
|
|
|
const tables = await prisma.$queryRaw<{ tablename: string }[]>`
|
|
SELECT tablename FROM pg_tables
|
|
WHERE schemaname = 'public' AND tablename NOT LIKE '_prisma_migrations'
|
|
`;
|
|
|
|
if (tables.length > 0) {
|
|
await prisma.$executeRawUnsafe(`
|
|
DROP SCHEMA public CASCADE;
|
|
CREATE SCHEMA public;
|
|
`);
|
|
console.log(`Dropped ${tables.length} tables`);
|
|
}
|
|
|
|
console.log('Running migrations...');
|
|
execSync('bunx prisma migrate deploy', { stdio: 'inherit' });
|
|
}
|
|
|
|
async function cleanupTestRecords(prisma: PrismaClient) {
|
|
console.log('Cleaning up test records...');
|
|
|
|
const playerWhere = buildLikeClause(TEST_PATTERNS.players);
|
|
const eventWhere = buildLikeClause(TEST_PATTERNS.events);
|
|
const userWhere = buildEmailLikeClause(TEST_PATTERNS.users);
|
|
|
|
await prisma.$executeRawUnsafe(`DELETE FROM events WHERE (${eventWhere});`);
|
|
console.log('Deleted test events');
|
|
|
|
await prisma.$executeRawUnsafe(`DELETE FROM players WHERE (${playerWhere});`);
|
|
console.log('Deleted test players');
|
|
|
|
await prisma.$executeRawUnsafe(`DELETE FROM users WHERE (${userWhere}) AND "playerId" IS NULL;`);
|
|
console.log('Deleted test users (without player associations)');
|
|
|
|
await prisma.$disconnect();
|
|
}
|
|
|
|
export default async function globalTeardown(config: FullConfig) {
|
|
console.log('\n=== Global Teardown ===');
|
|
|
|
const dbUrl = process.env.DATABASE_URL || '';
|
|
|
|
if (isCIDatabase()) {
|
|
console.log('CI environment - resetting database schema');
|
|
const prisma = createPrismaClient();
|
|
await resetDatabaseSchema(prisma);
|
|
await prisma.$disconnect();
|
|
} else if (isProductionDatabase()) {
|
|
console.log('Production environment - selective cleanup of test records');
|
|
const prisma = createPrismaClient();
|
|
await cleanupTestRecords(prisma);
|
|
} else {
|
|
console.log('Development environment - selective cleanup of test records');
|
|
const prisma = createPrismaClient();
|
|
await cleanupTestRecords(prisma);
|
|
}
|
|
|
|
console.log('=== Global Teardown Complete ===\n');
|
|
} |