u
This commit is contained in:
153
scripts/test-backup-service.js
Normal file
153
scripts/test-backup-service.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import assert from 'node:assert';
|
||||
import env from '../src/config/env.js';
|
||||
import {
|
||||
createHourlyBackup,
|
||||
listBackups,
|
||||
pruneBackups,
|
||||
getBackupServiceStatus,
|
||||
startBackupService,
|
||||
stopBackupService,
|
||||
resolveBackupPaths,
|
||||
} from '../src/services/backupService.js';
|
||||
import { closeStore } from '../src/db/store.js';
|
||||
|
||||
async function runTests() {
|
||||
console.log('--- Starting Backup Service Verification ---');
|
||||
|
||||
// 1. Verify path resolution and directories
|
||||
const paths = resolveBackupPaths();
|
||||
console.log(`Backup root dir: ${paths.rootDir}`);
|
||||
assert(paths.rootDir.endsWith('data/backups'), 'Backup root should be data/backups');
|
||||
|
||||
// 2. Execute a backup
|
||||
console.log('\n[Test 1] Running createHourlyBackup()...');
|
||||
const backupResult = await createHourlyBackup({ manual: true });
|
||||
assert(backupResult.success === true, 'Backup should report success');
|
||||
console.log(`Backup created: ${backupResult.id}, size: ${(backupResult.totalSizeBytes / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log(`Promoted to daily: ${backupResult.promotedDaily}, Promoted to monthly: ${backupResult.promotedMonthly}`);
|
||||
|
||||
// Verify files in the created hourly backup
|
||||
const hourlyBackupFolder = path.join(paths.hourlyDir, backupResult.id);
|
||||
assert(fs.existsSync(hourlyBackupFolder), 'Hourly backup folder should exist');
|
||||
assert(fs.existsSync(path.join(hourlyBackupFolder, 'vfs.tar.gz')), 'vfs.tar.gz should exist');
|
||||
assert(fs.existsSync(path.join(hourlyBackupFolder, 'storage.tar')), 'storage.tar should exist');
|
||||
assert(fs.existsSync(path.join(hourlyBackupFolder, 'manifest.json')), 'manifest.json should exist');
|
||||
if (env.dbProvider === 'sqlite') {
|
||||
const dbName = path.basename(env.sqlite.filename);
|
||||
assert(fs.existsSync(path.join(hourlyBackupFolder, dbName)), `${dbName} should exist`);
|
||||
console.log(`Verified SQLite database backup file: ${dbName}`);
|
||||
}
|
||||
|
||||
// Verify daily backup directory
|
||||
const { dateKey, monthKey } = backupResult.manifest;
|
||||
const dailyFolder = path.join(paths.dailyDir, `backup-${dateKey}`);
|
||||
assert(fs.existsSync(dailyFolder), 'Daily backup folder should exist');
|
||||
assert(fs.existsSync(path.join(dailyFolder, 'manifest.json')), 'Daily manifest should exist');
|
||||
|
||||
// Verify monthly backup directory
|
||||
const monthlyFolder = path.join(paths.monthlyDir, `backup-${monthKey}`);
|
||||
assert(fs.existsSync(monthlyFolder), 'Monthly backup folder should exist');
|
||||
assert(fs.existsSync(path.join(monthlyFolder, 'manifest.json')), 'Monthly manifest should exist');
|
||||
|
||||
// 3. Test listBackups and getBackupServiceStatus
|
||||
console.log('\n[Test 2] Testing listBackups() and getBackupServiceStatus()...');
|
||||
const status = getBackupServiceStatus();
|
||||
assert(typeof status.enabled === 'boolean', 'status.enabled should be boolean');
|
||||
assert(status.retention.hourlyMax === 50, 'hourly retention should be 50');
|
||||
assert(status.retention.dailyMax === 50, 'daily retention should be 50');
|
||||
|
||||
const listing = listBackups();
|
||||
assert(listing.hourly.length >= 1, 'Should have at least 1 hourly backup');
|
||||
assert(listing.daily.length >= 1, 'Should have at least 1 daily backup');
|
||||
assert(listing.monthly.length >= 1, 'Should have at least 1 monthly backup');
|
||||
console.log(`Counts -> hourly: ${listing.counts.hourly}, daily: ${listing.counts.daily}, monthly: ${listing.counts.monthly}`);
|
||||
|
||||
// 4. Test Retention Pruning (exceeding 50 entries)
|
||||
console.log('\n[Test 3] Testing retention pruning for > 50 entries...');
|
||||
// Create dummy hourly backup folders
|
||||
for (let i = 1; i <= 55; i++) {
|
||||
const dummyName = `backup-2020-01-01_${String(i).padStart(2, '0')}-00-00`;
|
||||
const dummyPath = path.join(paths.hourlyDir, dummyName);
|
||||
fs.mkdirSync(dummyPath, { recursive: true });
|
||||
fs.writeFileSync(path.join(dummyPath, 'manifest.json'), '{}');
|
||||
}
|
||||
|
||||
// Create dummy daily backup folders
|
||||
for (let i = 1; i <= 55; i++) {
|
||||
const dummyName = `backup-2020-01-${String(i).padStart(2, '0')}`;
|
||||
const dummyPath = path.join(paths.dailyDir, dummyName);
|
||||
fs.mkdirSync(dummyPath, { recursive: true });
|
||||
fs.writeFileSync(path.join(dummyPath, 'manifest.json'), '{}');
|
||||
}
|
||||
|
||||
// Create dummy monthly backup folders (should never be pruned!)
|
||||
for (let i = 1; i <= 55; i++) {
|
||||
const dummyName = `backup-2010-${String(i).padStart(2, '0')}`;
|
||||
const dummyPath = path.join(paths.monthlyDir, dummyName);
|
||||
fs.mkdirSync(dummyPath, { recursive: true });
|
||||
fs.writeFileSync(path.join(dummyPath, 'manifest.json'), '{}');
|
||||
}
|
||||
|
||||
const pruneResult = pruneBackups();
|
||||
console.log(`Pruned ${pruneResult.hourlyPruned} hourly, ${pruneResult.dailyPruned} daily`);
|
||||
assert(pruneResult.hourlyPruned >= 5, 'Should have pruned at least 5 hourly backups');
|
||||
assert(pruneResult.dailyPruned >= 5, 'Should have pruned at least 5 daily backups');
|
||||
|
||||
const listingAfterPrune = listBackups();
|
||||
assert(listingAfterPrune.counts.hourly <= 50, `Hourly count should be <= 50, got ${listingAfterPrune.counts.hourly}`);
|
||||
assert(listingAfterPrune.counts.daily <= 50, `Daily count should be <= 50, got ${listingAfterPrune.counts.daily}`);
|
||||
assert(listingAfterPrune.counts.monthly >= 55, `Monthly count should NOT be pruned (got ${listingAfterPrune.counts.monthly})`);
|
||||
console.log('Retention pruning verified: 50 hourly max, 50 daily max, monthly preserved forever.');
|
||||
|
||||
// Clean up dummy test backups
|
||||
for (let i = 1; i <= 55; i++) {
|
||||
const hPath = path.join(paths.hourlyDir, `backup-2020-01-01_${String(i).padStart(2, '0')}-00-00`);
|
||||
if (fs.existsSync(hPath)) fs.rmSync(hPath, { recursive: true, force: true });
|
||||
const dPath = path.join(paths.dailyDir, `backup-2020-01-${String(i).padStart(2, '0')}`);
|
||||
if (fs.existsSync(dPath)) fs.rmSync(dPath, { recursive: true, force: true });
|
||||
const mPath = path.join(paths.monthlyDir, `backup-2010-${String(i).padStart(2, '0')}`);
|
||||
if (fs.existsSync(mPath)) fs.rmSync(mPath, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
// 5. Test MSSQL warning behavior
|
||||
console.log('\n[Test 4] Testing MSSQL console warning behavior...');
|
||||
const originalProvider = env.dbProvider;
|
||||
let warningCaptured = false;
|
||||
const origWarn = console.warn;
|
||||
console.warn = (...args) => {
|
||||
const text = args.join(' ');
|
||||
if (text.includes('MSSQL database backup is not implemented')) {
|
||||
warningCaptured = true;
|
||||
}
|
||||
origWarn(...args);
|
||||
};
|
||||
|
||||
try {
|
||||
env.dbProvider = 'mssql';
|
||||
const mssqlBackup = await createHourlyBackup({ manual: true });
|
||||
assert(warningCaptured, 'Console warning must be printed when running on MSSQL');
|
||||
assert(mssqlBackup.manifest.files.database.backedUp === false, 'Database backup must be skipped on MSSQL');
|
||||
console.log('MSSQL warning and database skip verified.');
|
||||
} finally {
|
||||
console.warn = origWarn;
|
||||
env.dbProvider = originalProvider;
|
||||
}
|
||||
|
||||
// 6. Test Lifecycle methods
|
||||
console.log('\n[Test 5] Testing startBackupService() and stopBackupService()...');
|
||||
startBackupService();
|
||||
assert(getBackupServiceStatus().isRunning === true, 'Service should be running');
|
||||
await stopBackupService();
|
||||
assert(getBackupServiceStatus().isRunning === false, 'Service should be stopped');
|
||||
console.log('Service lifecycle start/stop verified.');
|
||||
|
||||
await closeStore();
|
||||
console.log('\n--- ALL BACKUP SERVICE TESTS PASSED ---');
|
||||
}
|
||||
|
||||
runTests().catch((err) => {
|
||||
console.error('Test failed:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user