feat: SDLC database separation for CI/testing #35
@@ -27,6 +27,29 @@ jobs:
|
|||||||
- name: Run unit tests
|
- name: Run unit tests
|
||||||
run: bun test src/__tests__/unit/ src/__tests__/*.test.tsx src/__tests__/auth-simple.test.ts
|
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:
|
analyze-bump-type:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: unit-tests
|
needs: unit-tests
|
||||||
|
|||||||
@@ -239,3 +239,28 @@ workflow-status:
|
|||||||
@echo "PR Workflow: Runs unit + acceptance tests on pull requests"
|
@echo "PR Workflow: Runs unit + acceptance tests on pull requests"
|
||||||
@echo "Test Workflow: Runs unit tests on all branch pushes"
|
@echo "Test Workflow: Runs unit tests on all branch pushes"
|
||||||
@echo "Release Workflow: Runs on main branch pushes (version bump + Docker build)"
|
@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
|
||||||
|
|||||||
@@ -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();
|
||||||
Reference in New Issue
Block a user