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