import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { databaseBackup } from '../backup' import { databaseRestore } from '../restore' import path from 'path' // Mock pg-dump-restore for integration tests vi.mock('pg-dump-restore', async (importOriginal) => { const actual = await importOriginal() as typeof import('pg-dump-restore') return { ...actual, pgDump: vi.fn().mockResolvedValue({ size: 1024 }), pgRestore: vi.fn().mockResolvedValue(undefined), } }) // Mock fs for integration tests vi.mock('fs', () => ({ promises: { mkdir: vi.fn().mockResolvedValue(undefined), chmod: vi.fn().mockResolvedValue(undefined), readdir: vi.fn().mockResolvedValue([]), stat: vi.fn().mockResolvedValue({ size: 1024, mtimeMs: Date.now(), mode: 0o600, mtime: new Date() }), access: vi.fn().mockResolvedValue(undefined), unlink: vi.fn().mockResolvedValue(undefined), writeFile: vi.fn().mockResolvedValue(undefined), rm: vi.fn().mockResolvedValue(undefined), utimes: vi.fn().mockResolvedValue(undefined), } })) // Mock node-cron vi.mock('node-cron', () => ({ default: { schedule: vi.fn().mockImplementation(() => ({ stop: vi.fn(), nextDate: vi.fn().mockReturnValue(new Date()), })), }, })) // Mock logger vi.mock('../logger', () => ({ logger: { db: vi.fn(), error: vi.fn(), api: vi.fn(), middleware: vi.fn(), }, })) describe('Database Backup Integration', () => { const testBackupDir = './test-backups' beforeEach(async () => { vi.clearAllMocks() // 设置测试环境变量 process.env.BACKUP_DIR = testBackupDir process.env.BACKUP_RETENTION_DAYS = '1' // 重置所有mock函数 const { promises } = await import('fs') const mockedPromises = vi.mocked(promises) // 重置所有mock实现 mockedPromises.mkdir.mockResolvedValue(undefined) mockedPromises.chmod.mockResolvedValue(undefined) mockedPromises.readdir.mockResolvedValue([]) mockedPromises.stat.mockResolvedValue({ size: 1024, mtimeMs: Date.now(), mode: 0o600, mtime: new Date() } as any) mockedPromises.access.mockResolvedValue(undefined) mockedPromises.unlink.mockResolvedValue(undefined) mockedPromises.writeFile.mockResolvedValue(undefined) mockedPromises.rm.mockResolvedValue(undefined) mockedPromises.utimes.mockResolvedValue(undefined) }) afterEach(() => { vi.restoreAllMocks() }) describe('Backup Creation', () => { it('应该成功创建备份文件', async () => { const backupFile = await databaseBackup.createBackup() console.debug('backupFile', backupFile) expect(backupFile).toBeDefined() expect(backupFile).toContain('.dump') // 验证文件已创建 const exists = await databaseBackup.backupExists(backupFile) expect(exists).toBe(true) // 验证文件权限 - 由于mock环境,我们验证chmod被正确调用 const fs = await import('fs') expect(vi.mocked(fs.promises.chmod)).toHaveBeenCalledWith(backupFile, 0o600) }) it('应该设置正确的文件权限', async () => { const backupFile = await databaseBackup.createBackup() console.debug('backupFile', backupFile) // 验证chmod被正确调用 const fs = await import('fs') expect(vi.mocked(fs.promises.chmod)).toHaveBeenCalledWith(backupFile, 0o600) }) }) describe('Backup Cleanup', () => { it('应该清理旧的备份文件', async () => { const fs = await import('fs') // 设置readdir返回测试文件 vi.mocked(fs.promises.readdir).mockResolvedValue(['backup-old.dump', 'backup-new.dump']) // 设置stat返回不同的时间 const now = Date.now() const oldFileTime = now - (2 * 24 * 60 * 60 * 1000) // 2天前 const newFileTime = now - (12 * 60 * 60 * 1000) // 12小时前 vi.mocked(fs.promises.stat) .mockResolvedValueOnce({ mtimeMs: oldFileTime } as any) .mockResolvedValueOnce({ mtimeMs: newFileTime } as any) // 执行清理 await databaseBackup.cleanupOldBackups() // 验证unlink被正确调用(只针对旧文件) expect(vi.mocked(fs.promises.unlink)).toHaveBeenCalledTimes(1) expect(vi.mocked(fs.promises.unlink)).toHaveBeenCalledWith(path.join('./backups', 'backup-old.dump')) }) }) describe('Backup Management', () => { it('应该能够检查备份文件是否存在', async () => { const backupFile = await databaseBackup.createBackup() const exists = await databaseBackup.backupExists(backupFile) expect(exists).toBe(true) const notExists = await databaseBackup.backupExists('/nonexistent/path.dump') expect(notExists).toBe(false) }) it('应该能够获取备份文件信息', async () => { const backupFile = await databaseBackup.createBackup() const info = await databaseBackup.getBackupInfo(backupFile) expect(info).toHaveProperty('size') expect(info).toHaveProperty('mtime') expect(info).toHaveProperty('formattedSize') expect(info.formattedSize).toBe('14 B') // 'mock backup data' 的长度 }) }) describe('Scheduled Backups', () => { it('应该启动和停止定时备份', async () => { const cron = await import('node-cron') databaseBackup.startScheduledBackups() expect(vi.mocked(cron.default.schedule)).toHaveBeenCalledWith('0 2 * * *', expect.any(Function)) databaseBackup.stopScheduledBackups() // 验证stop方法被调用 const scheduleInstance = vi.mocked(cron.default.schedule).mock.results[0].value expect(scheduleInstance.stop).toHaveBeenCalled() }) it('应该返回备份状态', () => { databaseBackup.startScheduledBackups() const status = databaseBackup.getBackupStatus() expect(status).toHaveProperty('scheduled') expect(status).toHaveProperty('nextRun') expect(status).toHaveProperty('lastRun') expect(status.scheduled).toBe(true) }) }) describe('Restore Integration', () => { it('应该能够找到最新备份', async () => { // 创建多个备份文件 await databaseBackup.createBackup() await new Promise(resolve => setTimeout(resolve, 100)) // 确保时间戳不同 await databaseBackup.createBackup() const latestBackup = await databaseRestore.findLatestBackup() expect(latestBackup).toBeDefined() expect(latestBackup).toContain('.dump') }) it('应该能够列出所有备份', async () => { // 创建多个备份文件 await databaseBackup.createBackup() await databaseBackup.createBackup() const backups = await databaseRestore.listBackups() expect(backups.length).toBe(2) expect(backups.every(b => b.endsWith('.dump'))).toBe(true) }) }) })