From f3a356d64bbc8ecde7295b97904e4ff22c2f3451 Mon Sep 17 00:00:00 2001 From: jackiettran <41605212+jackiettran@users.noreply.github.com> Date: Tue, 25 Nov 2025 21:35:09 -0500 Subject: [PATCH] test migration script --- backend/package.json | 2 +- backend/scripts/test-migrations.js | 225 +++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 backend/scripts/test-migrations.js diff --git a/backend/package.json b/backend/package.json index de9d797..e22fad8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -21,7 +21,7 @@ "db:migrate:undo:all": "sequelize-cli db:migrate:undo:all", "db:migrate:status": "sequelize-cli db:migrate:status", "db:create": "sequelize-cli db:create", - "test:migrations": "node scripts/test-migrations.js", + "test:migrations": "NODE_ENV=test node scripts/test-migrations.js", "alpha:add": "NODE_ENV=dev node scripts/manageAlphaInvitations.js add", "alpha:list": "NODE_ENV=dev node scripts/manageAlphaInvitations.js list", "alpha:revoke": "NODE_ENV=dev node scripts/manageAlphaInvitations.js revoke", diff --git a/backend/scripts/test-migrations.js b/backend/scripts/test-migrations.js new file mode 100644 index 0000000..74739a7 --- /dev/null +++ b/backend/scripts/test-migrations.js @@ -0,0 +1,225 @@ +#!/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); +});