84 lines
2.5 KiB
JavaScript
84 lines
2.5 KiB
JavaScript
|
|
// Migration Idempotency Integration Test
|
||
|
|
// This test requires a running PostgreSQL instance with DATABASE_URL configured in backend/.env.
|
||
|
|
// It runs ALL Postgres migrations twice (via run-all.js) to verify they are idempotent (safe to re-run),
|
||
|
|
// then checks that key tables and columns exist.
|
||
|
|
//
|
||
|
|
// Run separately: npx jest backend/__tests__/migrations-idempotency.integration.test.js --forceExit
|
||
|
|
|
||
|
|
const { execSync } = require('child_process');
|
||
|
|
const path = require('path');
|
||
|
|
|
||
|
|
// The real pool — NOT mocked. This hits the actual database.
|
||
|
|
const pool = require('../db');
|
||
|
|
|
||
|
|
const BACKEND_DIR = path.join(__dirname, '..');
|
||
|
|
|
||
|
|
function runAllMigrations() {
|
||
|
|
execSync('node migrations/run-all.js', {
|
||
|
|
cwd: BACKEND_DIR,
|
||
|
|
stdio: 'pipe',
|
||
|
|
timeout: 30000,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
afterAll(async () => {
|
||
|
|
await pool.end();
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('Migration Idempotency', () => {
|
||
|
|
it('runs all migrations twice without errors (idempotent)', () => {
|
||
|
|
// First run
|
||
|
|
runAllMigrations();
|
||
|
|
// Second run — should not throw if migrations are truly idempotent
|
||
|
|
runAllMigrations();
|
||
|
|
}, 30000);
|
||
|
|
|
||
|
|
it('key tables exist after migrations', async () => {
|
||
|
|
const expectedTables = [
|
||
|
|
'compliance_items',
|
||
|
|
'compliance_item_history',
|
||
|
|
'compliance_notes',
|
||
|
|
'jira_tickets',
|
||
|
|
'ivanti_fp_submissions',
|
||
|
|
];
|
||
|
|
|
||
|
|
const { rows } = await pool.query(`
|
||
|
|
SELECT table_name
|
||
|
|
FROM information_schema.tables
|
||
|
|
WHERE table_schema = 'public'
|
||
|
|
AND table_name = ANY($1)
|
||
|
|
`, [expectedTables]);
|
||
|
|
|
||
|
|
const foundTables = rows.map(r => r.table_name);
|
||
|
|
for (const table of expectedTables) {
|
||
|
|
expect(foundTables).toContain(table);
|
||
|
|
}
|
||
|
|
}, 30000);
|
||
|
|
|
||
|
|
it('compliance_item_history has expected columns', async () => {
|
||
|
|
const expectedColumns = [
|
||
|
|
'id',
|
||
|
|
'hostname',
|
||
|
|
'field_name',
|
||
|
|
'old_value',
|
||
|
|
'new_value',
|
||
|
|
'change_reason',
|
||
|
|
'changed_by',
|
||
|
|
'changed_at',
|
||
|
|
'metric_id',
|
||
|
|
];
|
||
|
|
|
||
|
|
const { rows } = await pool.query(`
|
||
|
|
SELECT column_name
|
||
|
|
FROM information_schema.columns
|
||
|
|
WHERE table_schema = 'public'
|
||
|
|
AND table_name = 'compliance_item_history'
|
||
|
|
`);
|
||
|
|
|
||
|
|
const foundColumns = rows.map(r => r.column_name);
|
||
|
|
for (const col of expectedColumns) {
|
||
|
|
expect(foundColumns).toContain(col);
|
||
|
|
}
|
||
|
|
}, 30000);
|
||
|
|
});
|