| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
- import { DatabaseBackup } from '../../../../src/server/utils/backup'
- import path from 'path'
- // Mock pg-dump-restore
- vi.mock('pg-dump-restore', () => ({
- pgDump: vi.fn().mockResolvedValue(undefined),
- pgRestore: vi.fn().mockResolvedValue(undefined),
- }))
- // Mock fs for 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),
- rm: vi.fn().mockResolvedValue(undefined),
- writeFile: vi.fn().mockResolvedValue(undefined),
- utimes: vi.fn().mockResolvedValue(undefined),
- }
- }))
- // Mock logger
- vi.mock('../../../../src/server/utils/logger', () => ({
- logger: {
- db: vi.fn(),
- error: vi.fn(),
- api: vi.fn(),
- middleware: vi.fn(),
- },
- }))
- describe('DatabaseBackup', () => {
- let backup: DatabaseBackup
- beforeEach(() => {
- vi.clearAllMocks()
- backup = DatabaseBackup.getInstance()
- })
- afterEach(() => {
- vi.restoreAllMocks()
- })
- describe('getInstance', () => {
- it('应该返回单例实例', () => {
- const instance1 = DatabaseBackup.getInstance()
- const instance2 = DatabaseBackup.getInstance()
- expect(instance1).toBe(instance2)
- })
- })
- describe('ensureBackupDir', () => {
- it('应该创建备份目录并设置权限', async () => {
- const fs = await import('fs')
- await backup.ensureBackupDir()
- expect(fs.promises.mkdir).toHaveBeenCalledWith('./backups', { recursive: true })
- expect(fs.promises.chmod).toHaveBeenCalledWith('./backups', 0o700)
- })
- it('应该在创建目录失败时抛出错误', async () => {
- const fs = await import('fs')
- const { logger } = await import('../../../../src/server/utils/logger')
- vi.mocked(fs.promises.mkdir).mockRejectedValueOnce(new Error('创建目录失败'))
- await expect(backup.ensureBackupDir()).rejects.toThrow('创建目录失败')
- expect(logger.error).toHaveBeenCalled()
- })
- })
- describe('getDbConfig', () => {
- it('应该返回正确的数据库配置', () => {
- process.env.DB_HOST = 'test-host'
- process.env.DB_PORT = '5433'
- process.env.DB_DATABASE = 'test-db'
- process.env.DB_USERNAME = 'test-user'
- process.env.DB_PASSWORD = 'test-password'
- const config = (backup as any).getDbConfig()
- expect(config).toEqual({
- host: 'test-host',
- port: 5433,
- database: 'test-db',
- username: 'test-user',
- password: 'test-password',
- })
- })
- it('应该使用默认值当环境变量未设置时', () => {
- delete process.env.DB_HOST
- delete process.env.DB_PORT
- delete process.env.DB_DATABASE
- delete process.env.DB_USERNAME
- delete process.env.DB_PASSWORD
- const config = (backup as any).getDbConfig()
- expect(config).toEqual({
- host: 'localhost',
- port: 5432,
- database: 'postgres',
- username: 'postgres',
- password: '',
- })
- })
- })
- describe('formatFileSize', () => {
- it('应该正确格式化文件大小', () => {
- const formatFileSize = (backup as any).formatFileSize
- expect(formatFileSize(0)).toBe('0 B')
- expect(formatFileSize(1024)).toBe('1 KB')
- expect(formatFileSize(1048576)).toBe('1 MB')
- expect(formatFileSize(1073741824)).toBe('1 GB')
- })
- })
- describe('backupExists', () => {
- it('应该返回true当备份文件存在时', async () => {
- const fs = await import('fs')
- const exists = await backup.backupExists('/path/to/backup.dump')
- expect(exists).toBe(true)
- expect(fs.promises.access).toHaveBeenCalledWith('/path/to/backup.dump')
- })
- it('应该返回false当备份文件不存在时', async () => {
- const fs = await import('fs')
- vi.mocked(fs.promises.access).mockRejectedValueOnce(new Error('文件不存在'))
- const exists = await backup.backupExists('/path/to/backup.dump')
- expect(exists).toBe(false)
- })
- })
- describe('cleanupOldBackups', () => {
- it('应该清理7天前的旧备份', async () => {
- const fs = await import('fs')
- const { logger } = await import('../../../../src/server/utils/logger')
- const now = Date.now()
- const oldFileTime = now - (8 * 24 * 60 * 60 * 1000) // 8天前
- const newFileTime = now - (6 * 24 * 60 * 60 * 1000) // 6天前
- vi.mocked(fs.promises.readdir).mockResolvedValue(['backup-old.dump', 'backup-new.dump'] as any)
- vi.mocked(fs.promises.stat)
- .mockResolvedValueOnce({ mtimeMs: oldFileTime } as any)
- .mockResolvedValueOnce({ mtimeMs: newFileTime } as any)
- await backup.cleanupOldBackups()
- expect(fs.promises.unlink).toHaveBeenCalledTimes(1)
- expect(fs.promises.unlink).toHaveBeenCalledWith(path.join('./backups', 'backup-old.dump'))
- expect(logger.db).toHaveBeenCalledWith('删除旧备份文件: backup-old.dump')
- })
- it('应该在清理失败时记录错误但不抛出', async () => {
- const fs = await import('fs')
- const { logger } = await import('../../../../src/server/utils/logger')
- vi.mocked(fs.promises.readdir).mockRejectedValueOnce(new Error('读取目录失败'))
- await expect(backup.cleanupOldBackups()).resolves.not.toThrow()
- expect(logger.error).toHaveBeenCalled()
- })
- })
- describe('startScheduledBackups', () => {
- it('应该启动定时备份任务', async () => {
- const { logger } = await import('../../../../src/server/utils/logger')
- backup.startScheduledBackups()
- // expect(cron.default.schedule).toHaveBeenCalledWith('0 2 * * *', expect.any(Function))
- expect(logger.db).toHaveBeenCalledWith('备份调度已启动: 0 2 * * *')
- })
- })
- describe('stopScheduledBackups', () => {
- it('应该停止定时备份任务', async () => {
- const { logger } = await import('../../../../src/server/utils/logger')
- // 先启动再停止
- backup.startScheduledBackups()
- backup.stopScheduledBackups()
- expect(logger.db).toHaveBeenCalledWith('备份调度已停止')
- })
- })
- })
|