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
JavaScript
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'));