| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
- import { databaseBackup } from '../backup'
- import { databaseRestore } from '../restore'
- import { promises as fs } from 'fs'
- import path from 'path'
- // Mock pg-dump-restore for integration tests
- vi.mock('pg-dump-restore', () => ({
- pgDump: vi.fn().mockImplementation(async (connectionOptions, dumpOptions) => {
- // 模拟创建备份文件
- const { filePath } = dumpOptions
- if (filePath) {
- const fs = await import('fs')
- await fs.promises.writeFile(filePath, 'mock backup data')
- }
- }),
- pgRestore: vi.fn().mockResolvedValue(undefined),
- }))
- // Mock fs with importOriginal for integration tests
- vi.mock('fs', async (importOriginal) => {
- const actual = await importOriginal()
- return {
- ...actual,
- promises: {
- ...actual.promises,
- mkdir: vi.fn().mockResolvedValue(undefined),
- chmod: vi.fn().mockResolvedValue(undefined),
- readdir: vi.fn().mockResolvedValue([]),
- stat: vi.fn().mockResolvedValue({ size: 1024, mtimeMs: Date.now() }),
- 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().mockReturnValue({
- stop: vi.fn(),
- nextDate: vi.fn().mockReturnValue(new Date()),
- }),
- },
- }))
- // Mock logger
- vi.mock('../logger', () => ({
- logger: {
- db: vi.fn(),
- error: 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'
- // 清理测试目录
- try {
- await fs.rm(testBackupDir, { recursive: true, force: true })
- } catch (error) {
- // 目录可能不存在
- }
- })
- afterEach(async () => {
- // 清理测试目录
- try {
- await fs.rm(testBackupDir, { recursive: true, force: true })
- } catch (error) {
- // 忽略错误
- }
- vi.restoreAllMocks()
- })
- describe('Backup Creation', () => {
- it('应该成功创建备份文件', async () => {
- const backupFile = await databaseBackup.createBackup()
- expect(backupFile).toBeDefined()
- expect(backupFile).toContain('.dump')
- // 验证文件已创建
- const exists = await databaseBackup.backupExists(backupFile)
- expect(exists).toBe(true)
- // 验证文件权限
- const stats = await fs.stat(backupFile)
- expect(stats.mode & 0o777).toBe(0o600) // 应该只有用户可读写
- })
- it('应该设置正确的文件权限', async () => {
- const backupFile = await databaseBackup.createBackup()
- const stats = await fs.stat(backupFile)
- expect(stats.mode & 0o777).toBe(0o600)
- })
- })
- describe('Backup Cleanup', () => {
- it('应该清理旧的备份文件', async () => {
- // 创建一些测试备份文件
- const now = Date.now()
- const oldFileTime = now - (2 * 24 * 60 * 60 * 1000) // 2天前
- const newFileTime = now - (12 * 60 * 60 * 1000) // 12小时前
- const oldBackup = path.join(testBackupDir, 'backup-old.dump')
- const newBackup = path.join(testBackupDir, 'backup-new.dump')
- await fs.mkdir(testBackupDir, { recursive: true })
- await fs.writeFile(oldBackup, 'old backup data')
- await fs.writeFile(newBackup, 'new backup data')
- // 修改文件时间
- await fs.utimes(oldBackup, new Date(oldFileTime), new Date(oldFileTime))
- await fs.utimes(newBackup, new Date(newFileTime), new Date(newFileTime))
- // 执行清理
- await databaseBackup.cleanupOldBackups()
- // 验证只有旧文件被删除
- const oldExists = await databaseBackup.backupExists(oldBackup)
- const newExists = await databaseBackup.backupExists(newBackup)
- expect(oldExists).toBe(false)
- expect(newExists).toBe(true)
- })
- })
- 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('应该启动和停止定时备份', () => {
- const cron = require('node-cron')
- databaseBackup.startScheduledBackups()
- expect(cron.schedule).toHaveBeenCalled()
- databaseBackup.stopScheduledBackups()
- // 验证stop方法被调用
- expect(cron.schedule().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)
- })
- })
- })
|