#!/usr/bin/env node /** * Migration Test Script * * Tests that all migrations can run successfully up and down. * This script: * 1. Connects to a test database * 2. Runs all migrations down (clean slate) * 3. Runs all migrations up * 4. Verifies tables were created * 5. Runs all migrations down (test rollback) * 6. Runs all migrations up again (test idempotency) * 7. Reports results * * Usage: * NODE_ENV=test npm run test:migrations * * Requires: * - Test database to exist (create with: npm run db:create) * - Environment variables set for test database connection */ const { execSync } = require("child_process"); const path = require("path"); // Colors for console output const colors = { reset: "\x1b[0m", green: "\x1b[32m", red: "\x1b[31m", yellow: "\x1b[33m", blue: "\x1b[34m", }; function log(message, color = colors.reset) { console.log(`${color}${message}${colors.reset}`); } function logStep(step, message) { log(`\n[${step}] ${message}`, colors.blue); } function logSuccess(message) { log(` ✓ ${message}`, colors.green); } function logError(message) { log(` ✗ ${message}`, colors.red); } function logWarning(message) { log(` ⚠ ${message}`, colors.yellow); } function runCommand(command, description) { try { log(` Running: ${command}`, colors.yellow); const output = execSync(command, { cwd: path.resolve(__dirname, ".."), encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], env: { ...process.env, NODE_ENV: process.env.NODE_ENV || "test" }, }); if (output.trim()) { console.log(output); } logSuccess(description); return { success: true, output }; } catch (error) { logError(`${description} failed`); console.error(error.stderr || error.message); return { success: false, error }; } } async function main() { log("\n========================================", colors.blue); log(" Migration Test Suite", colors.blue); log("========================================\n", colors.blue); const env = process.env.NODE_ENV; // Safety checks - only allow running against test database if (!env) { logError("NODE_ENV is not set!"); logError("This script will DELETE ALL DATA in the target database."); logError("You must explicitly set NODE_ENV=test to run this script."); log("\nUsage: NODE_ENV=test npm run test:migrations\n"); process.exit(1); } if (env.toLowerCase() !== "test") { logWarning(`Unrecognized NODE_ENV: ${env}`); logWarning("This script will DELETE ALL DATA in the target database."); logWarning("Recommended: NODE_ENV=test npm run test:migrations"); log(""); } log(`Environment: ${env}`); const results = { steps: [], passed: 0, failed: 0, }; function recordResult(step, success) { results.steps.push({ step, success }); if (success) { results.passed++; } else { results.failed++; } } // Step 1: Check migration status logStep(1, "Checking current migration status"); const statusResult = runCommand( "npx sequelize-cli db:migrate:status", "Migration status check" ); recordResult("Status check", statusResult.success); // Step 2: Undo all migrations (clean slate) logStep(2, "Undoing all migrations (clean slate)"); const undoAllResult = runCommand( "npx sequelize-cli db:migrate:undo:all", "Undo all migrations" ); recordResult("Undo all migrations", undoAllResult.success); if (!undoAllResult.success) { logWarning("Undo failed - database may already be empty, continuing..."); } // Step 3: Run all migrations up logStep(3, "Running all migrations up"); const migrateUpResult = runCommand( "npx sequelize-cli db:migrate", "Run all migrations" ); recordResult("Migrate up", migrateUpResult.success); if (!migrateUpResult.success) { logError("Migration up failed - cannot continue"); printSummary(results); process.exit(1); } // Step 4: Verify migration status shows all executed logStep(4, "Verifying all migrations executed"); const verifyResult = runCommand( "npx sequelize-cli db:migrate:status", "Verify migration status" ); recordResult("Verify status", verifyResult.success); // Step 5: Test rollback - undo all migrations logStep(5, "Testing rollback - undoing all migrations"); const rollbackResult = runCommand( "npx sequelize-cli db:migrate:undo:all", "Rollback all migrations" ); recordResult("Rollback", rollbackResult.success); if (!rollbackResult.success) { logError("Rollback failed - down migrations have issues"); printSummary(results); process.exit(1); } // Step 6: Test idempotency - run migrations up again logStep(6, "Testing idempotency - running migrations up again"); const idempotencyResult = runCommand( "npx sequelize-cli db:migrate", "Re-run all migrations" ); recordResult("Idempotency test", idempotencyResult.success); if (!idempotencyResult.success) { logError("Idempotency test failed - migrations may not be repeatable"); printSummary(results); process.exit(1); } // Step 7: Final status check logStep(7, "Final migration status"); const finalStatusResult = runCommand( "npx sequelize-cli db:migrate:status", "Final status check" ); recordResult("Final status", finalStatusResult.success); printSummary(results); if (results.failed > 0) { process.exit(1); } log("\nMigration tests completed successfully!", colors.green); process.exit(0); } function printSummary(results) { log("\n========================================", colors.blue); log(" Test Summary", colors.blue); log("========================================\n", colors.blue); results.steps.forEach(({ step, success }) => { if (success) { logSuccess(step); } else { logError(step); } }); log(`\nTotal: ${results.passed} passed, ${results.failed} failed`); } main().catch((error) => { logError("Unexpected error:"); console.error(error); process.exit(1); });