"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const database_1 = require("../../utils/database");
const migrations_1 = require("../../utils/migrations");
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
describe('Database Migration Integration Tests', () => {
    let dbManager;
    let migrationManager;
    let tempDbPath;
    let db;
    beforeEach(() => {
        tempDbPath = path.join(os.tmpdir(), `test-migrations-${Date.now()}.db`);
        dbManager = new database_1.DatabaseManager({
            filename: tempDbPath,
            maxSize: 10 * 1024 * 1024,
            walMode: true,
        });
        db = dbManager.getDatabase();
        migrationManager = new migrations_1.MigrationManager(dbManager);
    });
    afterEach(() => {
        dbManager.close();
        try {
            fs.unlinkSync(tempDbPath);
            fs.unlinkSync(`${tempDbPath}-wal`);
            fs.unlinkSync(`${tempDbPath}-shm`);
        }
        catch (_e) {
            // Ignore
        }
    });
    describe('Migration Management', () => {
        it('should create and retrieve migrations', () => {
            const migration = {
                version: '1.0.0',
                name: 'Add test table',
                description: 'Creates a test table for testing',
                up: 'CREATE TABLE test_table (id INTEGER PRIMARY KEY, name TEXT);',
                down: 'DROP TABLE test_table;',
                requiresBackup: true,
            };
            const migrationId = migrationManager.createMigration(migration);
            expect(migrationId).toBeDefined();
            expect(typeof migrationId).toBe('string');
            const retrieved = migrationManager.getMigration(migration.version);
            expect(retrieved).toBeDefined();
            expect(retrieved.name).toBe(migration.name);
            expect(retrieved.version).toBe(migration.version);
            expect(retrieved.up).toBe(migration.up);
            expect(retrieved.down).toBe(migration.down);
            expect(retrieved.requiresBackup).toBe(migration.requiresBackup);
        });
        it('should list all migrations', () => {
            const migrations = [
                { version: '1.0.0', name: 'Migration 1', up: 'SELECT 1;' },
                { version: '1.1.0', name: 'Migration 2', up: 'SELECT 2;' },
                { version: '1.2.0', name: 'Migration 3', up: 'SELECT 3;' },
            ];
            const migrationIds = migrations.map(m => migrationManager.createMigration(m));
            const allMigrations = migrationManager.listMigrations();
            expect(allMigrations.length).toBeGreaterThanOrEqual(3);
            const createdMigrations = allMigrations.filter(m => migrationIds.includes(m.id));
            expect(createdMigrations.length).toBe(3);
        });
        it('should filter migrations by applied status', () => {
            const migrations = [
                { version: '2.0.0', name: 'Applied Migration', up: 'SELECT 1;' },
                { version: '2.1.0', name: 'Pending Migration', up: 'SELECT 2;' },
            ];
            migrations.forEach(m => migrationManager.createMigration(m));
            // Apply one migration
            const appliedMigration = migrationManager.getMigration('2.0.0');
            db.prepare('UPDATE migrations SET applied_at = CURRENT_TIMESTAMP WHERE id = ?').run(appliedMigration.id);
            const appliedMigrations = migrationManager.listMigrations({ applied: true });
            const pendingMigrations = migrationManager.listMigrations({ pending: true });
            expect(appliedMigrations.some(m => m.version === '2.0.0')).toBe(true);
            expect(pendingMigrations.some(m => m.version === '2.1.0')).toBe(true);
            expect(pendingMigrations.some(m => m.version === '2.0.0')).toBe(false);
        });
        it('should enforce unique versions', () => {
            const migration1 = { version: '3.0.0', name: 'Migration 1', up: 'SELECT 1;' };
            const migration2 = { version: '3.0.0', name: 'Migration 2', up: 'SELECT 2;' };
            migrationManager.createMigration(migration1);
            expect(() => migrationManager.createMigration(migration2)).toThrow();
        });
        it('should calculate checksums for migrations', () => {
            const migration = {
                version: '4.0.0',
                name: 'Checksum Test',
                up: 'CREATE TABLE checksum_test (id INTEGER);',
                down: 'DROP TABLE checksum_test;',
            };
            migrationManager.createMigration(migration);
            const retrieved = migrationManager.getMigration(migration.version);
            expect(retrieved.checksum).toBeDefined();
            expect(retrieved.checksum).toHaveLength(16); // SHA-256 substring
        });
    });
    describe('Migration Status', () => {
        beforeEach(() => {
            // Create test migrations
            const migrations = [
                { version: '5.0.0', name: 'Migration 1', up: 'SELECT 1;' },
                { version: '5.1.0', name: 'Migration 2', up: 'SELECT 2;', requiresBackup: true },
                { version: '5.2.0', name: 'Migration 3', up: 'SELECT 3;' },
            ];
            migrations.forEach(m => migrationManager.createMigration(m));
            // Apply first migration
            const appliedMigration = migrationManager.getMigration('5.0.0');
            db.prepare('UPDATE migrations SET applied_at = CURRENT_TIMESTAMP WHERE id = ?').run(appliedMigration.id);
        });
        it('should provide comprehensive status', () => {
            const status = migrationManager.getStatus();
            expect(status.totalMigrations).toBeGreaterThanOrEqual(3);
            expect(status.appliedMigrations).toBeGreaterThanOrEqual(1);
            expect(status.pendingMigrations).toBeGreaterThanOrEqual(2);
            expect(status.pending.length).toBeGreaterThanOrEqual(2);
            expect(status.applied.length).toBeGreaterThanOrEqual(1);
            expect(status.lastMigration).toBeDefined();
            expect(status.lastMigration.version).toBe('0.4.0');
        });
        it('should identify migrations requiring backups', () => {
            const status = migrationManager.getStatus();
            const backupRequired = status.pending.find(m => m.requiresBackup);
            expect(backupRequired).toBeDefined();
            expect(backupRequired.version).toBe('5.1.0');
        });
        it('should handle empty migration state', () => {
            // Clear all migrations
            db.prepare('DELETE FROM migrations').run();
            const status = migrationManager.getStatus();
            expect(status.totalMigrations).toBe(0);
            expect(status.appliedMigrations).toBe(0);
            expect(status.pendingMigrations).toBe(0);
            expect(status.currentVersion).toBe('0.0.0');
            expect(status.lastMigration).toBeUndefined();
        });
    });
    describe('Migration Execution', () => {
        beforeEach(() => {
            // Create test table for migration testing
            db.exec(`
        CREATE TABLE IF NOT EXISTS migration_test (
          id INTEGER PRIMARY KEY,
          name TEXT
        );
      `);
        });
        it('should apply migration successfully', async () => {
            const migration = {
                version: '6.0.0',
                name: 'Add column test',
                up: 'ALTER TABLE migration_test ADD COLUMN email TEXT;',
                down: 'ALTER TABLE migration_test DROP COLUMN email;',
            };
            migrationManager.createMigration(migration);
            const result = await migrationManager.applyMigration('6.0.0', { dryRun: false });
            expect(result.success).toBe(true);
            expect(result.errors.length).toBe(0);
            expect(result.executionTime).toBeGreaterThanOrEqual(0);
            // Verify migration was marked as applied
            const appliedMigration = migrationManager.getMigration('6.0.0');
            expect(appliedMigration.appliedAt).toBeDefined();
            // Verify schema change
            const columns = db.prepare('PRAGMA table_info(migration_test)').all();
            expect(columns.some((col) => col.name === 'email')).toBe(true);
        });
        it('should perform dry run without changes', async () => {
            const migration = {
                version: '6.1.0',
                name: 'Dry run test',
                up: 'ALTER TABLE migration_test ADD COLUMN phone TEXT;',
            };
            migrationManager.createMigration(migration);
            const result = await migrationManager.applyMigration('6.1.0', { dryRun: true });
            expect(result.success).toBe(true);
            expect(result.warnings).toContain('Dry run - no changes applied');
            // Verify migration was NOT marked as applied
            const appliedMigration = migrationManager.getMigration('6.1.0');
            expect(appliedMigration.appliedAt).toBeNull();
            // Verify no schema change
            const columns = db.prepare('PRAGMA table_info(migration_test)').all();
            expect(columns.some((col) => col.name === 'phone')).toBe(false);
        });
        it('should rollback migration successfully', async () => {
            const migration = {
                version: '6.2.0',
                name: 'Rollback test',
                up: 'ALTER TABLE migration_test ADD COLUMN temp_column TEXT;',
                down: 'ALTER TABLE migration_test DROP COLUMN temp_column;',
            };
            migrationManager.createMigration(migration);
            // Apply migration first
            await migrationManager.applyMigration('6.2.0', { dryRun: false });
            // Verify column was added
            let columns = db.prepare('PRAGMA table_info(migration_test)').all();
            expect(columns.some((col) => col.name === 'temp_column')).toBe(true);
            // Rollback migration
            const result = await migrationManager.rollbackMigration('6.2.0', { dryRun: false });
            expect(result.success).toBe(true);
            expect(result.errors.length).toBe(0);
            // Verify migration was marked as rolled back
            const rolledBackMigration = migrationManager.getMigration('6.2.0');
            expect(rolledBackMigration.appliedAt).toBeNull();
            expect(rolledBackMigration.rollbackAt).toBeDefined();
            // Verify column was removed
            columns = db.prepare('PRAGMA table_info(migration_test)').all();
            expect(columns.some((col) => col.name === 'temp_column')).toBe(false);
        });
        it('should handle migration dependencies', async () => {
            const migrations = [
                { version: '7.0.0', name: 'Base migration', up: 'SELECT 1;' },
                {
                    version: '7.1.0',
                    name: 'Dependent migration',
                    up: 'SELECT 2;',
                    dependencies: ['7.0.0'],
                },
            ];
            migrations.forEach(m => migrationManager.createMigration(m));
            // Try to apply dependent migration without dependency
            const failResult = await migrationManager.applyMigration('7.1.0', { dryRun: false });
            expect(failResult.success).toBe(false);
            expect(failResult.errors).toContain('Dependency not satisfied: 7.0.0');
            // Apply base migration first
            await migrationManager.applyMigration('7.0.0', { dryRun: false });
            // Now dependent migration should work
            const successResult = await migrationManager.applyMigration('7.1.0', { dryRun: false });
            expect(successResult.success).toBe(true);
        });
        it('should validate SQL before execution', async () => {
            const migration = {
                version: '7.2.0',
                name: 'Invalid SQL',
                up: '', // Empty SQL
            };
            migrationManager.createMigration(migration);
            const result = await migrationManager.applyMigration('7.2.0', { dryRun: false });
            expect(result.success).toBe(false);
            expect(result.errors).toContain('Empty SQL statement');
        });
        it('should detect dangerous SQL operations', async () => {
            const migration = {
                version: '7.3.0',
                name: 'Dangerous SQL',
                up: 'DELETE FROM migration_test;', // DELETE without WHERE
            };
            migrationManager.createMigration(migration);
            const result = await migrationManager.applyMigration('7.3.0', { dryRun: false });
            expect(result.success).toBe(false);
            expect(result.errors).toContain('Potentially dangerous SQL detected');
        });
        it('should handle transaction rollback on error', async () => {
            const migration = {
                version: '7.4.0',
                name: 'Error migration',
                up: 'ALTER TABLE non_existent_table ADD COLUMN test TEXT;', // Should fail
            };
            migrationManager.createMigration(migration);
            const result = await migrationManager.applyMigration('7.4.0', { dryRun: false });
            expect(result.success).toBe(false);
            expect(result.errors.length).toBeGreaterThan(0);
            // Verify migration was NOT marked as applied
            const failedMigration = migrationManager.getMigration('7.4.0');
            expect(failedMigration.appliedAt).toBeNull();
        });
    });
    describe('Batch Operations', () => {
        beforeEach(() => {
            // Create multiple pending migrations
            const migrations = [
                { version: '8.0.0', name: 'Migration 1', up: 'SELECT 1;' },
                { version: '8.1.0', name: 'Migration 2', up: 'SELECT 2;' },
                { version: '8.2.0', name: 'Migration 3', up: 'SELECT 3;' },
            ];
            migrations.forEach(m => migrationManager.createMigration(m));
        });
        it('should apply all pending migrations', async () => {
            const results = await migrationManager.applyAllPending({ dryRun: false });
            expect(results.length).toBe(3);
            expect(results.every(r => r.success)).toBe(true);
            // Verify all migrations were applied
            const status = migrationManager.getStatus();
            expect(status.pendingMigrations).toBe(0);
            expect(status.appliedMigrations).toBeGreaterThanOrEqual(3);
        });
        it('should stop on error when configured', async () => {
            // Add a migration that will fail
            const badMigration = {
                version: '8.3.0',
                name: 'Bad Migration',
                up: 'INVALID SQL SYNTAX;',
            };
            migrationManager.createMigration(badMigration);
            const results = await migrationManager.applyAllPending({
                dryRun: false,
                stopOnError: true,
            });
            // Should have tried 4 migrations but stopped at the invalid one
            expect(results.length).toBeLessThanOrEqual(4);
            expect(results.some(r => !r.success)).toBe(true);
        });
        it('should continue on error when configured', async () => {
            // Add a migration that will fail
            const badMigration = {
                version: '8.4.0',
                name: 'Bad Migration',
                up: 'INVALID SQL SYNTAX;',
            };
            migrationManager.createMigration(badMigration);
            const results = await migrationManager.applyAllPending({
                dryRun: false,
                stopOnError: false,
            });
            // Should have tried all 4 migrations
            expect(results.length).toBe(4);
            expect(results.some(r => r.success)).toBe(true);
            expect(results.some(r => !r.success)).toBe(true);
        });
    });
    describe('Logging and Audit', () => {
        it('should log migration execution', async () => {
            const migration = {
                version: '9.0.0',
                name: 'Logging test',
                up: 'SELECT 1;',
            };
            migrationManager.createMigration(migration);
            await migrationManager.applyMigration('9.0.0', { dryRun: false });
            const logs = migrationManager.getMigrationLog('9.0.0');
            expect(logs.length).toBeGreaterThan(0);
            const logEntry = logs[0];
            expect(logEntry.version).toBe('9.0.0');
            expect(logEntry.action).toBe('apply');
            expect(logEntry.success).toBe(1); // SQLite boolean
            expect(logEntry.execution_time).toBeGreaterThanOrEqual(0);
        });
        it('should log rollback operations', async () => {
            const migration = {
                version: '9.1.0',
                name: 'Rollback logging test',
                up: 'SELECT 1;',
                down: 'SELECT 0;',
            };
            migrationManager.createMigration(migration);
            await migrationManager.applyMigration('9.1.0', { dryRun: false });
            await migrationManager.rollbackMigration('9.1.0', { dryRun: false });
            const logs = migrationManager.getMigrationLog('9.1.0');
            expect(logs.length).toBe(2);
            const applyLog = logs.find(l => l.action === 'apply');
            const rollbackLog = logs.find(l => l.action === 'rollback');
            expect(applyLog).toBeDefined();
            expect(rollbackLog).toBeDefined();
        });
        it('should limit log results', () => {
            const logs = migrationManager.getMigrationLog(undefined, 5);
            expect(logs.length).toBeLessThanOrEqual(5);
        });
        it('should log errors properly', async () => {
            const migration = {
                version: '9.2.0',
                name: 'Error logging test',
                up: 'INVALID SQL;',
            };
            migrationManager.createMigration(migration);
            const result = await migrationManager.applyMigration('9.2.0', { dryRun: false });
            expect(result.success).toBe(false);
            const logs = migrationManager.getMigrationLog('9.2.0');
            const errorLog = logs[0];
            expect(errorLog.success).toBe(0); // SQLite boolean
            expect(errorLog.errors).toBeDefined();
            const errors = JSON.parse(errorLog.errors);
            expect(errors.length).toBeGreaterThan(0);
        });
    });
    describe('Default Migrations', () => {
        it('should provide default migrations', () => {
            const defaultMigrations = migrations_1.MigrationManager.getDefaultMigrations();
            expect(defaultMigrations.length).toBeGreaterThan(0);
            expect(defaultMigrations[0]).toHaveProperty('version');
            expect(defaultMigrations[0]).toHaveProperty('name');
            expect(defaultMigrations[0]).toHaveProperty('up');
        });
        it('should create default migrations without errors', () => {
            const defaultMigrations = migrations_1.MigrationManager.getDefaultMigrations();
            // Get initial migration count
            const initialCount = migrationManager.listMigrations().length;
            for (const migration of defaultMigrations) {
                expect(() => migrationManager.createMigration(migration)).not.toThrow();
            }
            const createdMigrations = migrationManager.listMigrations();
            expect(createdMigrations.length).toBe(initialCount + defaultMigrations.length);
        });
        it('should have proper dependency chains', () => {
            const defaultMigrations = migrations_1.MigrationManager.getDefaultMigrations();
            // Check that dependencies exist
            const versions = defaultMigrations.map((m) => m.version);
            for (const migration of defaultMigrations) {
                if (migration.dependencies) {
                    for (const dep of migration.dependencies) {
                        expect(versions).toContain(dep);
                    }
                }
            }
        });
        it('should have rollback SQL for important migrations', () => {
            const defaultMigrations = migrations_1.MigrationManager.getDefaultMigrations();
            // Most default migrations should have rollback capability
            const migrationsWithRollback = defaultMigrations.filter((m) => m.down);
            expect(migrationsWithRollback.length).toBeGreaterThan(0);
        });
    });
    describe('Error Handling', () => {
        it('should handle non-existent migration application', async () => {
            await expect(migrationManager.applyMigration('non-existent')).rejects.toThrow('Migration not found');
        });
        it('should handle non-existent migration rollback', async () => {
            await expect(migrationManager.rollbackMigration('non-existent')).rejects.toThrow('Migration not found');
        });
        it('should handle double application', async () => {
            const migration = {
                version: '10.0.0',
                name: 'Double apply test',
                up: 'SELECT 1;',
            };
            migrationManager.createMigration(migration);
            await migrationManager.applyMigration('10.0.0', { dryRun: false });
            await expect(migrationManager.applyMigration('10.0.0', { dryRun: false })).rejects.toThrow('already applied');
        });
        it('should handle rollback without down SQL', async () => {
            const migration = {
                version: '10.1.0',
                name: 'No rollback test',
                up: 'SELECT 1;',
                // No down SQL
            };
            migrationManager.createMigration(migration);
            await migrationManager.applyMigration('10.1.0', { dryRun: false });
            await expect(migrationManager.rollbackMigration('10.1.0', { dryRun: false })).rejects.toThrow('no rollback SQL');
        });
        it('should handle rollback of non-applied migration', async () => {
            const migration = {
                version: '10.2.0',
                name: 'Not applied test',
                up: 'SELECT 1;',
                down: 'SELECT 0;',
            };
            migrationManager.createMigration(migration);
            await expect(migrationManager.rollbackMigration('10.2.0', { dryRun: false })).rejects.toThrow('not applied');
        });
    });
    describe('Edge Cases', () => {
        it('should handle migrations with no description', () => {
            const migration = {
                version: '11.0.0',
                name: 'No description',
                up: 'SELECT 1;',
            };
            expect(() => migrationManager.createMigration(migration)).not.toThrow();
            const retrieved = migrationManager.getMigration('11.0.0');
            expect(retrieved.description).toBeNull();
        });
        it('should handle migrations with no dependencies', () => {
            const migration = {
                version: '11.1.0',
                name: 'No dependencies',
                up: 'SELECT 1;',
            };
            migrationManager.createMigration(migration);
            // Should apply without dependency check
            expect(async () => {
                await migrationManager.applyMigration('11.1.0', { dryRun: false });
            }).not.toThrow();
        });
        it('should handle empty migration list for batch operations', async () => {
            // Clear all migrations
            db.prepare('DELETE FROM migrations').run();
            const results = await migrationManager.applyAllPending({ dryRun: false });
            expect(results.length).toBe(0);
        });
        it('should handle very long SQL statements', () => {
            const longSQL = 'SELECT ' + '1,'.repeat(1000) + '1;';
            const migration = {
                version: '11.2.0',
                name: 'Long SQL',
                up: longSQL,
            };
            expect(() => migrationManager.createMigration(migration)).not.toThrow();
            const retrieved = migrationManager.getMigration('11.2.0');
            expect(retrieved.up).toBe(longSQL);
        });
    });
});
