const { DatabaseBackupService } = require('../databaseBackup'); const { db } = require('../../database/db'); const fs = require('fs').promises; const path = require('path'); const crypto = require('crypto'); // Mock dependencies jest.mock('../../database/db'); jest.mock('../../utils/logger'); jest.mock('../emailProcessor'); jest.mock('child_process'); describe('DatabaseBackupService', () => { let service; let mockExecAsync; beforeEach(() => { service = new DatabaseBackupService(); mockExecAsync = jest.fn(); // Reset mocks jest.clearAllMocks(); // Mock execAsync const childProcess = require('child_process'); childProcess.exec = jest.fn((cmd, opts, callback) => { if (callback) { callback(null, { stdout: 'ok', stderr: '' }); } }); }); afterEach(async () => { // Cleanup test files try { await fs.rmdir('/tmp/test-backup', { recursive: true }); } catch (e) { // Ignore } }); describe('calculateChecksum', () => { it('should calculate SHA256 checksum of a file', async () => { const testFile = '/tmp/test-checksum.txt'; const testContent = 'Hello, World!'; await fs.writeFile(testFile, testContent); const checksum = await service.calculateChecksum(testFile); // Expected checksum for "Hello, World!" const expectedChecksum = crypto .createHash('sha256') .update(testContent) .digest('hex'); expect(checksum).toBe(expectedChecksum); await fs.unlink(testFile); }); }); describe('getTableChecksums', () => { it('should get checksums for all tables', async () => { // Mock getTables service.getTables = jest.fn().mockResolvedValue(['events', 'photos']); // Mock SQLite response db.raw = jest.fn() .mockResolvedValueOnce([{ row_count: 10, data_sum: 1000 }]) .mockResolvedValueOnce([{ row_count: 20, data_sum: 2000 }]); const checksums = await service.getTableChecksums(); expect(checksums).toHaveProperty('events'); expect(checksums).toHaveProperty('photos'); expect(checksums.events.rowCount).toBe(10); expect(checksums.photos.rowCount).toBe(20); expect(checksums.events.checksum).toBeDefined(); expect(checksums.photos.checksum).toBeDefined(); }); }); describe('getTables', () => { it('should get list of tables for SQLite', async () => { service.dbType = 'sqlite'; db.raw = jest.fn().mockResolvedValue([ { name: 'events' }, { name: 'photos' }, { name: 'admin_users' } ]); const tables = await service.getTables(); expect(tables).toEqual(['events', 'photos', 'admin_users']); expect(db.raw).toHaveBeenCalledWith(expect.stringContaining('sqlite_master')); }); it('should get list of tables for PostgreSQL', async () => { service.dbType = 'postgresql'; db.raw = jest.fn().mockResolvedValue({ rows: [ { table_name: 'events' }, { table_name: 'photos' }, { table_name: 'admin_users' } ] }); const tables = await service.getTables(); expect(tables).toEqual(['events', 'photos', 'admin_users']); expect(db.raw).toHaveBeenCalledWith(expect.stringContaining('information_schema.tables')); }); }); describe('getDatabaseSize', () => { it('should get database size for SQLite', async () => { service.dbType = 'sqlite'; const mockSize = 1024 * 1024 * 10; // 10MB // Mock fs.stat const originalStat = fs.stat; fs.stat = jest.fn().mockResolvedValue({ size: mockSize }); const size = await service.getDatabaseSize(); expect(size).toBe(mockSize); fs.stat = originalStat; }); it('should get database size for PostgreSQL', async () => { service.dbType = 'postgresql'; const mockSize = 1024 * 1024 * 100; // 100MB db.raw = jest.fn().mockResolvedValue({ rows: [{ size: mockSize.toString() }] }); const size = await service.getDatabaseSize(); expect(size).toBe(mockSize); expect(db.raw).toHaveBeenCalledWith(expect.stringContaining('pg_database_size')); }); }); describe('compressFile', () => { it('should compress file and return stats', async () => { const testFile = '/tmp/test-compress.txt'; const compressedFile = '/tmp/test-compress.txt.gz'; // Create test file with repetitive content (compresses well) const testContent = 'Hello, World! '.repeat(1000); await fs.writeFile(testFile, testContent); const stats = await service.compressFile(testFile, compressedFile); expect(stats.originalSize).toBeGreaterThan(0); expect(stats.compressedSize).toBeGreaterThan(0); expect(stats.compressedSize).toBeLessThan(stats.originalSize); expect(parseFloat(stats.compressionRatio)).toBeGreaterThan(0); // Cleanup await fs.unlink(testFile); await fs.unlink(compressedFile); }); }); describe('backup configuration', () => { it('should get backup configuration from database', async () => { const mockConfig = [ { setting_key: 'database_backup_enabled', setting_value: 'true' }, { setting_key: 'database_backup_compress', setting_value: 'true' }, { setting_key: 'database_backup_retention_days', setting_value: '30' } ]; db.mockReturnValue({ where: jest.fn().mockReturnThis(), select: jest.fn().mockResolvedValue(mockConfig) }); const config = await service.getBackupConfig(); expect(config.database_backup_enabled).toBe(true); expect(config.database_backup_compress).toBe(true); expect(config.database_backup_retention_days).toBe(30); }); }); describe('cleanupOldBackups', () => { it('should delete old backup files and records', async () => { const oldBackups = [ { id: 1, file_path: '/backup/old1.sql.gz' }, { id: 2, file_path: '/backup/old2.sql.gz' } ]; db.mockReturnValue({ where: jest.fn().mockReturnThis(), select: jest.fn().mockResolvedValue(oldBackups), delete: jest.fn().mockResolvedValue(1) }); // Mock fs.unlink fs.unlink = jest.fn().mockResolvedValue(undefined); await service.cleanupOldBackups(30); expect(fs.unlink).toHaveBeenCalledTimes(2); expect(fs.unlink).toHaveBeenCalledWith('/backup/old1.sql.gz'); expect(fs.unlink).toHaveBeenCalledWith('/backup/old2.sql.gz'); }); }); describe('progress tracking', () => { it('should update and retrieve progress', () => { expect(service.getProgress()).toBeNull(); service.updateProgress('Testing...', { step: 1 }); const progress = service.getProgress(); expect(progress.message).toBe('Testing...'); expect(progress.details.step).toBe(1); expect(progress.timestamp).toBeDefined(); }); }); describe('backup history', () => { it('should retrieve backup history', async () => { const mockHistory = [ { id: 1, started_at: new Date(), completed_at: new Date(), status: 'completed', file_size_bytes: 1024000 } ]; db.mockReturnValue({ orderBy: jest.fn().mockReturnThis(), limit: jest.fn().mockResolvedValue(mockHistory) }); const history = await service.getBackupHistory(10); expect(history).toEqual(mockHistory); expect(history.length).toBe(1); }); }); });