UNPKG

powerbackup

Version:

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

395 lines (343 loc) โ€ข 13.7 kB
#!/usr/bin/env node import { log } from '../utils/logger.js'; import chalk from 'chalk'; // Mock test framework for Node.js const testResults = { passed: 0, failed: 0, total: 0 }; function describe(name, fn) { console.log(chalk.blue(`\n๐Ÿ“‹ 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 Logger describe('Logger Tests', () => { it('should have all required log methods', () => { expect(log.info).toBeTruthy(); expect(log.success).toBeTruthy(); expect(log.warn).toBeTruthy(); expect(log.error).toBeTruthy(); expect(log.debug).toBeTruthy(); }); it('should have specialized logging functions', () => { expect(log.backup).toBeTruthy(); expect(log.restore).toBeTruthy(); expect(log.encryption).toBeTruthy(); expect(log.compression).toBeTruthy(); expect(log.database).toBeTruthy(); expect(log.table).toBeTruthy(); }); it('should have backup logging methods', () => { expect(log.backup.start).toBeTruthy(); expect(log.backup.progress).toBeTruthy(); expect(log.backup.complete).toBeTruthy(); expect(log.backup.error).toBeTruthy(); }); it('should have restore logging methods', () => { expect(log.restore.start).toBeTruthy(); expect(log.restore.progress).toBeTruthy(); expect(log.restore.complete).toBeTruthy(); expect(log.restore.error).toBeTruthy(); }); }); // Test Configuration Loading describe('Configuration Tests', () => { it('should load configuration from file', async () => { const fs = await import('fs/promises'); const path = await import('path'); 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.databases).toBeTruthy(); expect(Array.isArray(config.databases)).toBeTruthy(); } catch (error) { // Config file might not exist in test environment expect(error).toBeInstanceOf(Error); } }); it('should validate database configuration structure', () => { const validConfig = { name: 'test-db', type: 'mysql', url: 'mysql://user:pass@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 } }; expect(validConfig.name).toBe('test-db'); expect(validConfig.type).toBe('mysql'); expect(validConfig.url).toContain('mysql://'); expect(validConfig.keep.hourly).toBe(24); expect(validConfig.test_restore.enabled).toBeTruthy(); }); }); // Test SQL Parsing describe('SQL Parsing Tests', () => { it('should extract table names from CREATE TABLE statements', () => { const sqlContent = ` CREATE TABLE \`users\` ( id INT PRIMARY KEY, name VARCHAR(255) ); CREATE TABLE \`posts\` ( id INT PRIMARY KEY, title VARCHAR(255) ); `; 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']); expect(tables.length).toBe(2); }); it('should handle SQL with no tables', () => { const sqlContent = 'SELECT * FROM users;'; 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([]); expect(tables.length).toBe(0); }); }); // Test File Operations describe('File Operation Tests', () => { it('should handle file path operations', async () => { const path = await import('path'); const fs = await import('fs/promises'); const testPath = path.join('test', 'backup', 'file.sql'); expect(testPath).toContain('test'); expect(testPath).toContain('backup'); expect(testPath).toContain('file.sql'); }); it('should validate file extensions', () => { const testFiles = [ 'backup.sql.gz', 'backup.sql.gz.gpg', 'backup.sql', 'backup.txt' ]; const gzFiles = testFiles.filter(f => f.endsWith('.gz')); const gpgFiles = testFiles.filter(f => f.endsWith('.gpg')); const sqlFiles = testFiles.filter(f => f.endsWith('.sql')); expect(gzFiles).toEqual(['backup.sql.gz']); expect(gpgFiles).toEqual(['backup.sql.gz.gpg']); expect(sqlFiles).toEqual(['backup.sql']); }); }); // Test URL Parsing describe('URL Parsing Tests', () => { it('should parse MySQL URLs correctly', () => { const url = 'mysql://user:password@localhost:3306/database'; const parsed = new URL(url); expect(parsed.protocol).toBe('mysql:'); expect(parsed.username).toBe('user'); expect(parsed.password).toBe('password'); expect(parsed.hostname).toBe('localhost'); expect(parsed.port).toBe('3306'); expect(parsed.pathname).toBe('/database'); }); it('should parse PostgreSQL URLs correctly', () => { const url = 'postgresql://user:password@localhost:5432/database'; const parsed = new URL(url); expect(parsed.protocol).toBe('postgresql:'); expect(parsed.username).toBe('user'); expect(parsed.password).toBe('password'); expect(parsed.hostname).toBe('localhost'); expect(parsed.port).toBe('5432'); expect(parsed.pathname).toBe('/database'); }); it('should handle URLs without port', () => { const url = 'mysql://user:password@localhost/database'; const parsed = new URL(url); expect(parsed.hostname).toBe('localhost'); expect(parsed.port).toBe(''); }); }); // Test Backup File Naming describe('Backup File Naming Tests', () => { it('should generate proper backup filenames', () => { const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); const dbName = 'test-db'; const fileName = `${dbName}_${timestamp}.sql`; expect(fileName).toContain(dbName); expect(fileName).toContain('.sql'); // More flexible regex that matches the actual format expect(fileName).toMatch(/^test-db_\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}\.sql$/); }); it('should handle compressed and encrypted filenames', () => { 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'); expect(compressedFile.endsWith('.gz')).toBeTruthy(); expect(encryptedFile.endsWith('.gpg')).toBeTruthy(); }); }); // Test Validation Functions describe('Validation Tests', () => { it('should validate database types', () => { const validTypes = ['mysql', 'postgres']; const testType = 'mysql'; expect(validTypes.includes(testType)).toBeTruthy(); expect(validTypes.includes('invalid')).toBeFalsy(); }); it('should validate retention policies', () => { const retention = { hourly: 24, daily: 7, weekly: 4, monthly: 12, yearly: 0 }; expect(retention.hourly).toBeGreaterThan(0); expect(retention.daily).toBeGreaterThan(0); expect(retention.weekly).toBeGreaterThan(0); expect(retention.monthly).toBeGreaterThan(0); expect(retention.yearly).toBeGreaterThanOrEqual(0); }); it('should validate test restore configuration', () => { const testRestore = { enabled: true, verify_query: 'SELECT COUNT(*) FROM information_schema.tables', hour: 3 }; expect(testRestore.enabled).toBeTruthy(); expect(testRestore.verify_query).toContain('SELECT'); expect(testRestore.hour).toBeGreaterThanOrEqual(0); expect(testRestore.hour).toBeLessThanOrEqual(23); }); }); // Test Error Handling describe('Error Handling 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 databases = []; const hasDatabases = databases && databases.length > 0; expect(hasDatabases).toBeFalsy(); }); it('should validate required 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 Summary describe('Test Summary', () => { it('should complete all tests successfully', () => { console.log(chalk.cyan('\n๐Ÿ“Š 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} tests failed`); } console.log(chalk.green('\n๐ŸŽ‰ All tests passed successfully!\n')); }); }); // Run tests console.log(chalk.cyan('๐Ÿงช Running PowerBackup Unit Tests...\n'));