UNPKG

powerbackup

Version:

Multi-Database Backup & Restore Tool v2.4.2 with Beautiful Logging, REST API, Enhanced Binary Detection & Debug Tools

446 lines (385 loc) • 16.2 kB
#!/usr/bin/env node import { log } from '../utils/logger.js'; import chalk from 'chalk'; import fs from 'fs/promises'; import path from 'path'; // Mock test framework for Node.js const testResults = { passed: 0, failed: 0, total: 0 }; function describe(name, fn) { console.log(chalk.blue(`\nšŸ”— Integration Test Suite: ${name}`)); fn(); } function it(name, fn) { testResults.total++; try { fn(); testResults.passed++; console.log(chalk.green(` āœ… ${name}`)); } catch (error) { testResults.failed++; console.log(chalk.red(` āŒ ${name} - ${error.message}`)); } } function expect(actual) { return { toBe: (expected) => { if (actual !== expected) { throw new Error(`Expected ${actual} to be ${expected}`); } }, toEqual: (expected) => { if (JSON.stringify(actual) !== JSON.stringify(expected)) { throw new Error(`Expected ${JSON.stringify(actual)} to equal ${JSON.stringify(expected)}`); } }, toContain: (expected) => { if (!actual.includes(expected)) { throw new Error(`Expected ${actual} to contain ${expected}`); } }, toBeInstanceOf: (expected) => { if (!(actual instanceof expected)) { throw new Error(`Expected ${actual} to be instance of ${expected.name}`); } }, toBeTruthy: () => { if (!actual) { throw new Error(`Expected ${actual} to be truthy`); } }, toBeFalsy: () => { if (actual) { throw new Error(`Expected ${actual} to be falsy`); } }, toBeGreaterThan: (expected) => { if (actual <= expected) { throw new Error(`Expected ${actual} to be greater than ${expected}`); } }, toBeGreaterThanOrEqual: (expected) => { if (actual < expected) { throw new Error(`Expected ${actual} to be greater than or equal to ${expected}`); } }, toBeLessThanOrEqual: (expected) => { if (actual > expected) { throw new Error(`Expected ${actual} to be less than or equal to ${expected}`); } }, toMatch: (expected) => { if (!expected.test(actual)) { throw new Error(`Expected ${actual} to match ${expected}`); } }, not: { toThrow: () => { try { if (typeof actual === 'function') { actual(); } } catch (error) { throw new Error(`Expected function not to throw, but it threw: ${error.message}`); } } } }; } // Test Configuration Integration describe('Configuration Integration Tests', () => { it('should load and validate configuration structure', async () => { try { const configPath = path.resolve(process.cwd(), './config/config.json'); const content = await fs.readFile(configPath, 'utf8'); const config = JSON.parse(content); expect(config).toBeTruthy(); expect(config.backup_dir).toBeTruthy(); expect(config.databases).toBeTruthy(); expect(Array.isArray(config.databases)).toBeTruthy(); // Validate each database configuration for (const db of config.databases) { expect(db.name).toBeTruthy(); expect(db.type).toBeTruthy(); expect(['mysql', 'postgres'].includes(db.type)).toBeTruthy(); expect(db.url || db.url_env).toBeTruthy(); } } catch (error) { // Config might not exist in test environment expect(error).toBeInstanceOf(Error); } }); it('should validate backup directory structure', async () => { const backupDir = '../../backups'; const testDbDir = path.join(backupDir, 'test-db', 'hourly'); expect(backupDir).toContain('backups'); expect(testDbDir).toContain('test-db'); expect(testDbDir).toContain('hourly'); }); }); // Test Logger Integration describe('Logger Integration Tests', () => { it('should log messages with proper formatting', () => { const testMessage = 'Test log message'; // Test that logging functions exist and are callable expect(() => log.info(testMessage)).not.toThrow(); expect(() => log.success(testMessage)).not.toThrow(); expect(() => log.warn(testMessage)).not.toThrow(); expect(() => log.error(testMessage)).not.toThrow(); }); it('should use specialized logging functions', () => { expect(() => log.backup.start('test-db')).not.toThrow(); expect(() => log.backup.progress('users', 1, 4)).not.toThrow(); expect(() => log.backup.complete('test-db', 'backup.sql')).not.toThrow(); expect(() => log.restore.start('test-db')).not.toThrow(); expect(() => log.table.list(['users', 'posts'])).not.toThrow(); }); }); // Test File System Integration describe('File System Integration Tests', () => { it('should handle file path operations correctly', async () => { const testPath = path.join('test', 'backup', 'file.sql'); const pathParts = testPath.split(path.sep); expect(pathParts).toContain('test'); expect(pathParts).toContain('backup'); expect(pathParts).toContain('file.sql'); }); it('should validate file extensions correctly', () => { const testCases = [ { file: 'backup.sql.gz', isGz: true, isGpg: false }, { file: 'backup.sql.gz.gpg', isGz: false, isGpg: true }, { file: 'backup.sql', isGz: false, isGpg: false }, { file: 'backup.txt', isGz: false, isGpg: false } ]; for (const testCase of testCases) { const isGz = testCase.file.endsWith('.gz') && !testCase.file.endsWith('.gpg'); const isGpg = testCase.file.endsWith('.gpg'); expect(isGz).toBe(testCase.isGz); expect(isGpg).toBe(testCase.isGpg); } }); }); // Test URL Parsing Integration describe('URL Parsing Integration Tests', () => { it('should parse MySQL URLs correctly', () => { const testUrls = [ 'mysql://user:pass@localhost:3306/db', 'mysql://user:pass@localhost/db', 'mysql://user@localhost/db' ]; for (const url of testUrls) { const parsed = new URL(url); expect(parsed.protocol).toBe('mysql:'); expect(parsed.hostname).toBe('localhost'); expect(parsed.pathname).toContain('/db'); } }); it('should parse PostgreSQL URLs correctly', () => { const testUrls = [ 'postgresql://user:pass@localhost:5432/db', 'postgresql://user:pass@localhost/db', 'postgresql://user@localhost/db' ]; for (const url of testUrls) { const parsed = new URL(url); expect(parsed.protocol).toBe('postgresql:'); expect(parsed.hostname).toBe('localhost'); expect(parsed.pathname).toContain('/db'); } }); }); // Test SQL Content Validation describe('SQL Content Validation Tests', () => { it('should validate SQL dump content structure', () => { const validSqlContent = ` -- PowerBackup MySQL dump -- Created at: 2024-01-01T12:00:00.000Z SET FOREIGN_KEY_CHECKS=0; CREATE TABLE \`users\` ( id INT PRIMARY KEY, name VARCHAR(255) ); INSERT INTO \`users\` VALUES (1, 'John'); SET FOREIGN_KEY_CHECKS=1; `; expect(validSqlContent).toContain('PowerBackup'); expect(validSqlContent).toContain('CREATE TABLE'); expect(validSqlContent).toContain('INSERT INTO'); expect(validSqlContent).toContain('SET FOREIGN_KEY_CHECKS'); }); it('should extract table names from SQL content', () => { const sqlContent = ` CREATE TABLE \`users\` (id INT); CREATE TABLE \`posts\` (id INT); CREATE TABLE \`comments\` (id INT); `; const tableMatches = sqlContent.match(/CREATE TABLE `([^`]+)`/g); const tables = tableMatches ? tableMatches.map(match => { const tableName = match.match(/CREATE TABLE `([^`]+)`/)[1]; return tableName; }) : []; expect(tables).toEqual(['users', 'posts', 'comments']); expect(tables.length).toBe(3); }); }); // Test Backup File Naming Integration describe('Backup File Naming Integration Tests', () => { it('should generate consistent backup filenames', () => { const dbName = 'test-db'; const timestamp = '2024-01-01T12-00-00'; const fileName = `${dbName}_${timestamp}.sql`; expect(fileName).toContain(dbName); expect(fileName).toContain(timestamp); expect(fileName).toContain('.sql'); expect(fileName).toBe('test-db_2024-01-01T12-00-00.sql'); }); it('should handle file extensions correctly', () => { const baseFile = 'test-db_2024-01-01T12-00-00.sql'; const compressedFile = baseFile + '.gz'; const encryptedFile = compressedFile + '.gpg'; expect(compressedFile).toBe('test-db_2024-01-01T12-00-00.sql.gz'); expect(encryptedFile).toBe('test-db_2024-01-01T12-00-00.sql.gz.gpg'); // Test extension detection expect(compressedFile.endsWith('.gz')).toBeTruthy(); expect(encryptedFile.endsWith('.gpg')).toBeTruthy(); expect(!compressedFile.endsWith('.gpg')).toBeTruthy(); }); }); // Test Configuration Validation Integration describe('Configuration Validation Integration Tests', () => { it('should validate database configuration completeness', () => { const validConfig = { name: 'test-db', type: 'mysql', url: 'mysql://localhost/db', keep: { hourly: 24, daily: 7, weekly: 4, monthly: 12, yearly: 0 }, test_restore: { enabled: true, verify_query: 'SELECT COUNT(*) FROM information_schema.tables', hour: 3 } }; // Required fields expect(validConfig.name).toBeTruthy(); expect(validConfig.type).toBeTruthy(); expect(validConfig.url).toBeTruthy(); // Valid database type expect(['mysql', 'postgres'].includes(validConfig.type)).toBeTruthy(); // Valid retention policy expect(validConfig.keep.hourly).toBeGreaterThan(0); expect(validConfig.keep.daily).toBeGreaterThan(0); expect(validConfig.keep.weekly).toBeGreaterThan(0); expect(validConfig.keep.monthly).toBeGreaterThan(0); expect(validConfig.keep.yearly).toBeGreaterThanOrEqual(0); // Valid test restore configuration expect(validConfig.test_restore.enabled).toBeTruthy(); expect(validConfig.test_restore.verify_query).toContain('SELECT'); expect(validConfig.test_restore.hour).toBeGreaterThanOrEqual(0); expect(validConfig.test_restore.hour).toBeLessThanOrEqual(23); }); it('should handle missing optional configuration gracefully', () => { const minimalConfig = { name: 'test-db', type: 'mysql', url: 'mysql://localhost/db' }; // Should not throw for missing optional fields expect(minimalConfig.name).toBeTruthy(); expect(minimalConfig.type).toBeTruthy(); expect(minimalConfig.url).toBeTruthy(); // Optional fields should be undefined expect(minimalConfig.keep).toBeFalsy(); expect(minimalConfig.test_restore).toBeFalsy(); }); }); // Test Error Handling Integration describe('Error Handling Integration Tests', () => { it('should handle missing configuration gracefully', () => { const config = null; const databases = config?.databases || []; expect(databases).toEqual([]); expect(databases.length).toBe(0); }); it('should handle empty database arrays', () => { const config = { databases: [] }; const hasDatabases = config.databases && config.databases.length > 0; expect(hasDatabases).toBeFalsy(); }); it('should validate required configuration fields', () => { const requiredFields = ['name', 'type', 'url']; const testConfig = { name: 'test-db', type: 'mysql', url: 'mysql://localhost/db' }; const missingFields = requiredFields.filter(field => !testConfig[field]); expect(missingFields).toEqual([]); // Test with missing field const incompleteConfig = { name: 'test-db', type: 'mysql' }; const missingFields2 = requiredFields.filter(field => !incompleteConfig[field]); expect(missingFields2).toEqual(['url']); }); }); // Test CLI Integration describe('CLI Integration Tests', () => { it('should have all required CLI commands', () => { const expectedCommands = [ 'list-dbs', 'add-db', 'create-now', 'test-restore', 'actual-restore', 'list-tables', 'restore-table', 'interactive-restore', 'list-backups' ]; for (const command of expectedCommands) { expect(command).toBeTruthy(); expect(typeof command).toBe('string'); } }); it('should validate command argument patterns', () => { const commandPatterns = [ { command: 'list-dbs', args: 0 }, { command: 'add-db', args: 0 }, { command: 'create-now', args: 1 }, { command: 'test-restore', args: 1 }, { command: 'actual-restore', args: 1 }, { command: 'list-tables', args: 1 }, { command: 'restore-table', args: 2 }, { command: 'interactive-restore', args: 1 }, { command: 'list-backups', args: 1 } ]; for (const pattern of commandPatterns) { expect(pattern.command).toBeTruthy(); expect(pattern.args).toBeGreaterThanOrEqual(0); } }); }); // Test Summary describe('Integration Test Summary', () => { it('should complete all integration tests successfully', () => { console.log(chalk.cyan('\nšŸ“Š Integration Test Results Summary:')); console.log(chalk.green(` āœ… Passed: ${testResults.passed}`)); console.log(chalk.red(` āŒ Failed: ${testResults.failed}`)); console.log(chalk.blue(` šŸ“‹ Total: ${testResults.total}`)); const successRate = ((testResults.passed / testResults.total) * 100).toFixed(1); console.log(chalk.yellow(` šŸ“ˆ Success Rate: ${successRate}%`)); if (testResults.failed > 0) { throw new Error(`${testResults.failed} integration tests failed`); } console.log(chalk.green('\nšŸŽ‰ All integration tests passed successfully!\n')); }); }); // Run integration tests console.log(chalk.cyan('šŸ”— Running PowerBackup Integration Tests...\n'));