414 lines
12 KiB
JavaScript
Raw Normal View History

const { SQLiteBackup, BackupUtils } = require('../lib/index.js');
const path = require('path');
const fs = require('fs');
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
/**
* Simple test suite for SQLite Backup Library
*/
class TestRunner {
constructor() {
this.tests = [];
this.passed = 0;
this.failed = 0;
}
test(name, testFn) {
this.tests.push({ name, testFn });
}
async run() {
console.log('🧪 Running SQLite Backup Library Tests\n');
for (const test of this.tests) {
try {
console.log(`🔍 Testing: ${test.name}`);
await test.testFn();
console.log(`✅ PASS: ${test.name}\n`);
this.passed++;
} catch (error) {
console.error(`❌ FAIL: ${test.name}`);
console.error(` Error: ${error.message}\n`);
this.failed++;
}
}
console.log('📊 Test Results:');
console.log(` ✅ Passed: ${this.passed}`);
console.log(` ❌ Failed: ${this.failed}`);
console.log(` 📈 Total: ${this.tests.length}`);
if (this.failed > 0) {
process.exit(1);
}
}
}
// Test utilities
function assert(condition, message) {
if (!condition) {
throw new Error(message || 'Assertion failed');
}
}
function assertEquals(actual, expected, message) {
if (actual !== expected) {
throw new Error(message || `Expected ${expected}, got ${actual}`);
}
}
function assertExists(filePath, message) {
if (!fs.existsSync(filePath)) {
throw new Error(message || `File does not exist: ${filePath}`);
}
}
// Setup and teardown
async function setupTestEnvironment() {
const testDir = './test-data';
const dbPath = path.join(testDir, 'test.db');
const backupDir = path.join(testDir, 'backups');
// Clean up any existing test data
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
// Create test directory
fs.mkdirSync(testDir, { recursive: true });
// Create test database
await execAsync(`sqlite3 "${dbPath}" "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT); INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com'), ('Jane Smith', 'jane@example.com');"`);
return { testDir, dbPath, backupDir };
}
function cleanupTestEnvironment(testDir) {
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
}
// Initialize test runner
const runner = new TestRunner();
// Test: Basic backup creation
runner.test('Basic backup creation', async () => {
const { testDir, dbPath, backupDir } = await setupTestEnvironment();
try {
const backup = new SQLiteBackup({
databasePath: dbPath,
backupDirectory: backupDir
});
const result = await backup.createBackup({
filename: 'test-backup.db',
includeTimestamp: false,
verifyIntegrity: true
});
assert(result.success, 'Backup should succeed');
assertExists(result.backupPath, 'Backup file should exist');
assert(result.size > 0, 'Backup should have positive size');
assert(result.checksum, 'Backup should have checksum');
} finally {
cleanupTestEnvironment(testDir);
}
});
// Test: Backup with timestamp
runner.test('Backup with timestamp', async () => {
const { testDir, dbPath, backupDir } = await setupTestEnvironment();
try {
const backup = new SQLiteBackup({
databasePath: dbPath,
backupDirectory: backupDir
});
const result = await backup.createBackup({
includeTimestamp: true,
verifyIntegrity: true
});
assert(result.success, 'Backup should succeed');
assert(result.filename.includes('-'), 'Filename should include timestamp');
assertExists(result.backupPath, 'Backup file should exist');
} finally {
cleanupTestEnvironment(testDir);
}
});
// Test: Different backup methods
runner.test('Different backup methods', async () => {
const { testDir, dbPath, backupDir } = await setupTestEnvironment();
try {
const backup = new SQLiteBackup({
databasePath: dbPath,
backupDirectory: backupDir
});
const methods = ['backup', 'copy'];
for (const method of methods) {
const result = await backup.createBackup({
filename: `test-${method}.db`,
includeTimestamp: false,
method: method
});
assert(result.success, `${method} backup should succeed`);
assertEquals(result.method, method, `Method should be ${method}`);
assertExists(result.backupPath, `${method} backup file should exist`);
}
} finally {
cleanupTestEnvironment(testDir);
}
});
// Test: Backup verification
runner.test('Backup verification', async () => {
const { testDir, dbPath, backupDir } = await setupTestEnvironment();
try {
const backup = new SQLiteBackup({
databasePath: dbPath,
backupDirectory: backupDir
});
const result = await backup.createBackup({
filename: 'verify-test.db',
includeTimestamp: false,
verifyIntegrity: true
});
assert(result.success, 'Backup should succeed');
const isValid = await backup.verifyBackup(result.backupPath);
assert(isValid, 'Backup should be valid');
} finally {
cleanupTestEnvironment(testDir);
}
});
// Test: List backups
runner.test('List backups', async () => {
const { testDir, dbPath, backupDir } = await setupTestEnvironment();
try {
const backup = new SQLiteBackup({
databasePath: dbPath,
backupDirectory: backupDir
});
// Create multiple backups
await backup.createBackup({ filename: 'backup1.db', includeTimestamp: false });
await backup.createBackup({ filename: 'backup2.db', includeTimestamp: false });
await backup.createBackup({ filename: 'backup3.db', includeTimestamp: false });
const backups = await backup.listBackups({
includeChecksums: true
});
assertEquals(backups.length, 3, 'Should have 3 backups');
backups.forEach(backup => {
assert(backup.filename, 'Backup should have filename');
assert(backup.path, 'Backup should have path');
assert(backup.size > 0, 'Backup should have positive size');
assert(backup.created, 'Backup should have creation date');
});
} finally {
cleanupTestEnvironment(testDir);
}
});
// Test: Cleanup by retention days
runner.test('Cleanup by retention days', async () => {
const { testDir, dbPath, backupDir } = await setupTestEnvironment();
try {
const backup = new SQLiteBackup({
databasePath: dbPath,
backupDirectory: backupDir
});
// Create a backup
const result = await backup.createBackup({
filename: 'old-backup.db',
includeTimestamp: false
});
// Manually change the file's modification time to make it "old"
const oldTime = new Date(Date.now() - (10 * 24 * 60 * 60 * 1000)); // 10 days ago
fs.utimesSync(result.backupPath, oldTime, oldTime);
// Run cleanup with 7 day retention
const cleanupResult = await backup.cleanup({
retentionDays: 7
});
assert(cleanupResult.success, 'Cleanup should succeed');
assertEquals(cleanupResult.removed, 1, 'Should remove 1 old backup');
} finally {
cleanupTestEnvironment(testDir);
}
});
// Test: Cleanup by max backups
runner.test('Cleanup by max backups', async () => {
const { testDir, dbPath, backupDir } = await setupTestEnvironment();
try {
const backup = new SQLiteBackup({
databasePath: dbPath,
backupDirectory: backupDir
});
// Create 5 backups
for (let i = 1; i <= 5; i++) {
await backup.createBackup({
filename: `backup${i}.db`,
includeTimestamp: false
});
// Small delay to ensure different modification times
await new Promise(resolve => setTimeout(resolve, 100));
}
// Keep only 3 most recent
const cleanupResult = await backup.cleanup({
maxBackups: 3
});
assert(cleanupResult.success, 'Cleanup should succeed');
assertEquals(cleanupResult.removed, 2, 'Should remove 2 old backups');
assertEquals(cleanupResult.remainingFiles, 3, 'Should have 3 remaining files');
} finally {
cleanupTestEnvironment(testDir);
}
});
// Test: Restore backup
runner.test('Restore backup', async () => {
const { testDir, dbPath, backupDir } = await setupTestEnvironment();
try {
const backup = new SQLiteBackup({
databasePath: dbPath,
backupDirectory: backupDir
});
// Create backup
const backupResult = await backup.createBackup({
filename: 'restore-test.db',
includeTimestamp: false
});
assert(backupResult.success, 'Backup creation should succeed');
// Restore to a new location
const restorePath = path.join(testDir, 'restored.db');
const restoreResult = await backup.restore(backupResult.backupPath, {
targetPath: restorePath,
createBackupBeforeRestore: false
});
assert(restoreResult.success, 'Restore should succeed');
assertEquals(restoreResult.restoredTo, restorePath, 'Should restore to correct path');
assertExists(restorePath, 'Restored file should exist');
// Verify restored database has same content
const isValid = await BackupUtils.validateDatabase(restorePath);
assert(isValid, 'Restored database should be valid');
} finally {
cleanupTestEnvironment(testDir);
}
});
// Test: BackupUtils functions
runner.test('BackupUtils functions', async () => {
// Test formatSize
assertEquals(BackupUtils.formatSize(0), '0 B', 'Should format 0 bytes');
assertEquals(BackupUtils.formatSize(1024), '1.00 KB', 'Should format KB');
assertEquals(BackupUtils.formatSize(1048576), '1.00 MB', 'Should format MB');
// Test formatDuration
assertEquals(BackupUtils.formatDuration(500), '500ms', 'Should format milliseconds');
assertEquals(BackupUtils.formatDuration(5000), '5.00s', 'Should format seconds');
assertEquals(BackupUtils.formatDuration(120000), '2.00m', 'Should format minutes');
// Test validateDatabase
const { testDir, dbPath } = await setupTestEnvironment();
try {
const isValid = await BackupUtils.validateDatabase(dbPath);
assert(isValid, 'Valid database should return true');
// Test with non-existent file
const nonExistentPath = './test-non-existent-' + Date.now() + '.db';
const invalidResult = await BackupUtils.validateDatabase(nonExistentPath);
assert(!invalidResult, 'Non-existent database should return false');
} finally {
cleanupTestEnvironment(testDir);
}
});
// Test: Error handling
runner.test('Error handling', async () => {
// Test with non-existent database
const nonExistentPath = './test-non-existent-' + Date.now() + '.db';
try {
new SQLiteBackup({
databasePath: nonExistentPath
});
assert(false, 'Should throw error for non-existent database');
} catch (error) {
assert(error.message.includes('not found'), 'Should throw appropriate error');
}
// Test cleanup without retention options
const { testDir, dbPath } = await setupTestEnvironment();
try {
const backup = new SQLiteBackup({
databasePath: dbPath,
backupDirectory: path.join(testDir, 'backups')
});
try {
await backup.cleanup({});
assert(false, 'Should throw error without retention options');
} catch (error) {
assert(error.message.includes('retentionDays or maxBackups'), 'Should throw appropriate error');
}
} finally {
cleanupTestEnvironment(testDir);
}
});
// Run all tests
if (require.main === module) {
runner.run().catch(error => {
console.error('❌ Test runner failed:', error);
process.exit(1);
});
}
module.exports = { runner };