From 30ea4e4e86dc7ea7e8262256629c630c894677b5 Mon Sep 17 00:00:00 2001 From: David Gwilliam Date: Sun, 3 May 2026 16:31:45 -0700 Subject: [PATCH] ci: add acceptance tests job with CI database and sync-dev recipe --- .gitea/workflows/pr.yml | 23 ++++++ justfile | 25 ++++++ scripts/sync-prod-to-dev.js | 157 ++++++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 scripts/sync-prod-to-dev.js diff --git a/.gitea/workflows/pr.yml b/.gitea/workflows/pr.yml index 099b130..ba86eaf 100644 --- a/.gitea/workflows/pr.yml +++ b/.gitea/workflows/pr.yml @@ -27,6 +27,29 @@ jobs: - name: Run unit tests run: bun test src/__tests__/unit/ src/__tests__/*.test.tsx src/__tests__/auth-simple.test.ts + acceptance-tests: + runs-on: ubuntu-latest + container: + image: docker.notsosm.art/euchre-camp/ci-base:latest + options: --user root + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies + run: bun install + + - name: Generate Prisma client + run: bun x prisma generate + env: + DATABASE_URL: postgresql://user:pass@localhost:5432/dummy + + - name: Run acceptance tests + run: DATABASE_URL="${{ vars.CI_DATABASE_URL }}" bun test:acceptance + env: + CI: true + analyze-bump-type: runs-on: ubuntu-latest needs: unit-tests diff --git a/justfile b/justfile index e4cb07f..25d35b3 100644 --- a/justfile +++ b/justfile @@ -239,3 +239,28 @@ workflow-status: @echo "PR Workflow: Runs unit + acceptance tests on pull requests" @echo "Test Workflow: Runs unit tests on all branch pushes" @echo "Release Workflow: Runs on main branch pushes (version bump + Docker build)" + +# --- SDLC Database Operations --- + +# Sync production data to development database (manual operation) +sync-dev: + @echo "Syncing production data to development database..." + node scripts/sync-prod-to-dev.js + +# Run acceptance tests against production database (opt-in, manual) +test-prod: + @echo "โš ๏ธ WARNING: This will run tests against the PRODUCTION database!" + @echo " All test records will be cleaned up after the run." + @echo "" + @read -p "Are you sure you want to continue? (y/N) " confirm; \ + if [ "$$confirm" != "y" ]; then \ + echo "Cancelled."; \ + else \ + DATABASE_URL="$$PROD_DATABASE_URL" bun test:acceptance; \ + fi + +# Reset CI database (for local CI testing) +reset-ci-db: + @echo "Resetting CI database..." + psql "$CI_DATABASE_URL" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;" + bunx prisma migrate deploy diff --git a/scripts/sync-prod-to-dev.js b/scripts/sync-prod-to-dev.js new file mode 100644 index 0000000..cdbaf34 --- /dev/null +++ b/scripts/sync-prod-to-dev.js @@ -0,0 +1,157 @@ +#!/usr/bin/env node + +/** + * Sync production data to development database + * + * This script copies real data from production to development, filtering out + * test records to keep the dev database clean for testing. + */ + +const { execSync } = require('child_process'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const PROD_DB_URL = process.env.PROD_DATABASE_URL; +const DEV_DB_URL = process.env.DEV_DATABASE_URL; + +if (!PROD_DB_URL || !DEV_DB_URL) { + console.error('โŒ Missing environment variables'); + console.error('Please set PROD_DATABASE_URL and DEV_DATABASE_URL'); + console.error(''); + console.error('Example:'); + console.error(' PROD_DATABASE_URL="postgresql://user:pass@host:5432/euchre_camp" \\'); + console.error(' DEV_DATABASE_URL="postgresql://user:pass@host:5432/euchre_camp_dev" \\'); + console.error(' node scripts/sync-prod-to-dev.js'); + process.exit(1); +} + +if (PROD_DB_URL.includes('_dev') || PROD_DB_URL.includes('_ci')) { + console.error('โŒ PROD_DATABASE_URL appears to be a non-production database!'); + console.error(' This script should only be used with the production database.'); + process.exit(1); +} + +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 buildLikeClause(patterns) { + return patterns.map(p => `name LIKE '${p}'`).join(' OR '); +} + +function buildEmailLikeClause(patterns) { + return patterns.map(p => `email LIKE '${p}'`).join(' OR '); +} + +async function main() { + console.log('๐Ÿ”„ Syncing production data to development database'); + console.log('====================================================\n'); + + const readline = require('readline'); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); + + console.log('โš ๏ธ WARNING: This will OVERWRITE the development database!'); + console.log(' Production data will be copied, with test records excluded.'); + console.log(''); + console.log('Source:', PROD_DB_URL.replace(/:[^:@]+@/, ':***@')); + console.log('Target:', DEV_DB_URL.replace(/:[^:@]+@/, ':***@')); + console.log(''); + + rl.question('Type "sync" to confirm: ', async (answer) => { + if (answer !== 'sync') { + console.log('โŒ Cancelled'); + rl.close(); + process.exit(0); + } + + rl.close(); + + try { + console.log('\n๐Ÿ“ฆ Dumping production data...'); + + const dumpFile = path.join(os.tmpdir(), `prod_dump_${Date.now()}.sql`); + + execSync(`pg_dump "${PROD_DB_URL}" -f "${dumpFile}" --no-owner --no-acl`, { + stdio: 'inherit' + }); + console.log('โœ… Dump created'); + + console.log('\n๐Ÿ—‘๏ธ Clearing development database...'); + execSync(`psql "${DEV_DB_URL}" -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"`, { + stdio: 'inherit' + }); + console.log('โœ… Development database cleared'); + + console.log('\n๐Ÿ“ฅ Restoring to development database...'); + execSync(`psql "${DEV_DB_URL}" -f "${dumpFile}"`, { + stdio: 'inherit' + }); + console.log('โœ… Data restored'); + + console.log('\n๐Ÿงน Cleaning up test records in dev database...'); + + const playerWhere = buildLikeClause(TEST_PATTERNS.players); + const eventWhere = buildLikeClause(TEST_PATTERNS.events); + const userWhere = buildEmailLikeClause(TEST_PATTERNS.users); + + execSync(`psql "${DEV_DB_URL}" -c "DELETE FROM events WHERE (${eventWhere});"`, { + stdio: 'inherit' + }); + console.log(' Deleted test events'); + + execSync(`psql "${DEV_DB_URL}" -c "DELETE FROM players WHERE (${playerWhere});"`, { + stdio: 'inherit' + }); + console.log(' Deleted test players'); + + execSync(`psql "${DEV_DB_URL}" -c "DELETE FROM users WHERE (${userWhere}) AND \"playerId\" IS NULL;"`, { + stdio: 'inherit' + }); + console.log(' Deleted test users'); + + fs.unlinkSync(dumpFile); + console.log('\n๐Ÿงน Cleaned up temporary dump file'); + + console.log('\nโœ… Sync complete!'); + console.log('\n๐Ÿ“Š Summary:'); + console.log(' - Production data copied to development'); + console.log(' - Test records filtered out'); + console.log(' - Development database is now a mirror of production (minus test data)'); + + } catch (error) { + console.error('\nโŒ Sync failed:', error.message); + process.exit(1); + } + }); +} + +main(); \ No newline at end of file