2
0

backup.integration.test.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
  2. import { databaseBackup } from '../backup'
  3. import { databaseRestore } from '../restore'
  4. import { promises as fs } from 'fs'
  5. import path from 'path'
  6. // Mock pg-dump-restore for integration tests
  7. vi.mock('pg-dump-restore', () => ({
  8. pgDump: vi.fn().mockImplementation(async (connectionOptions, dumpOptions) => {
  9. // 模拟创建备份文件
  10. const { filePath } = dumpOptions
  11. if (filePath) {
  12. const fs = await import('fs')
  13. await fs.promises.writeFile(filePath, 'mock backup data')
  14. }
  15. }),
  16. pgRestore: vi.fn().mockResolvedValue(undefined),
  17. }))
  18. // Mock fs with importOriginal for integration tests
  19. vi.mock('fs', async (importOriginal) => {
  20. const actual = await importOriginal()
  21. return {
  22. ...actual,
  23. promises: {
  24. ...actual.promises,
  25. mkdir: vi.fn().mockResolvedValue(undefined),
  26. chmod: vi.fn().mockResolvedValue(undefined),
  27. readdir: vi.fn().mockResolvedValue([]),
  28. stat: vi.fn().mockResolvedValue({ size: 1024, mtimeMs: Date.now() }),
  29. access: vi.fn().mockResolvedValue(undefined),
  30. unlink: vi.fn().mockResolvedValue(undefined),
  31. writeFile: vi.fn().mockResolvedValue(undefined),
  32. rm: vi.fn().mockResolvedValue(undefined),
  33. utimes: vi.fn().mockResolvedValue(undefined),
  34. },
  35. }
  36. })
  37. // Mock node-cron
  38. vi.mock('node-cron', () => ({
  39. default: {
  40. schedule: vi.fn().mockReturnValue({
  41. stop: vi.fn(),
  42. nextDate: vi.fn().mockReturnValue(new Date()),
  43. }),
  44. },
  45. }))
  46. // Mock logger
  47. vi.mock('../logger', () => ({
  48. logger: {
  49. db: vi.fn(),
  50. error: vi.fn(),
  51. },
  52. }))
  53. describe('Database Backup Integration', () => {
  54. const testBackupDir = './test-backups'
  55. beforeEach(async () => {
  56. vi.clearAllMocks()
  57. // 设置测试环境变量
  58. process.env.BACKUP_DIR = testBackupDir
  59. process.env.BACKUP_RETENTION_DAYS = '1'
  60. // 清理测试目录
  61. try {
  62. await fs.rm(testBackupDir, { recursive: true, force: true })
  63. } catch (error) {
  64. // 目录可能不存在
  65. }
  66. })
  67. afterEach(async () => {
  68. // 清理测试目录
  69. try {
  70. await fs.rm(testBackupDir, { recursive: true, force: true })
  71. } catch (error) {
  72. // 忽略错误
  73. }
  74. vi.restoreAllMocks()
  75. })
  76. describe('Backup Creation', () => {
  77. it('应该成功创建备份文件', async () => {
  78. const backupFile = await databaseBackup.createBackup()
  79. expect(backupFile).toBeDefined()
  80. expect(backupFile).toContain('.dump')
  81. // 验证文件已创建
  82. const exists = await databaseBackup.backupExists(backupFile)
  83. expect(exists).toBe(true)
  84. // 验证文件权限
  85. const stats = await fs.stat(backupFile)
  86. expect(stats.mode & 0o777).toBe(0o600) // 应该只有用户可读写
  87. })
  88. it('应该设置正确的文件权限', async () => {
  89. const backupFile = await databaseBackup.createBackup()
  90. const stats = await fs.stat(backupFile)
  91. expect(stats.mode & 0o777).toBe(0o600)
  92. })
  93. })
  94. describe('Backup Cleanup', () => {
  95. it('应该清理旧的备份文件', async () => {
  96. // 创建一些测试备份文件
  97. const now = Date.now()
  98. const oldFileTime = now - (2 * 24 * 60 * 60 * 1000) // 2天前
  99. const newFileTime = now - (12 * 60 * 60 * 1000) // 12小时前
  100. const oldBackup = path.join(testBackupDir, 'backup-old.dump')
  101. const newBackup = path.join(testBackupDir, 'backup-new.dump')
  102. await fs.mkdir(testBackupDir, { recursive: true })
  103. await fs.writeFile(oldBackup, 'old backup data')
  104. await fs.writeFile(newBackup, 'new backup data')
  105. // 修改文件时间
  106. await fs.utimes(oldBackup, new Date(oldFileTime), new Date(oldFileTime))
  107. await fs.utimes(newBackup, new Date(newFileTime), new Date(newFileTime))
  108. // 执行清理
  109. await databaseBackup.cleanupOldBackups()
  110. // 验证只有旧文件被删除
  111. const oldExists = await databaseBackup.backupExists(oldBackup)
  112. const newExists = await databaseBackup.backupExists(newBackup)
  113. expect(oldExists).toBe(false)
  114. expect(newExists).toBe(true)
  115. })
  116. })
  117. describe('Backup Management', () => {
  118. it('应该能够检查备份文件是否存在', async () => {
  119. const backupFile = await databaseBackup.createBackup()
  120. const exists = await databaseBackup.backupExists(backupFile)
  121. expect(exists).toBe(true)
  122. const notExists = await databaseBackup.backupExists('/nonexistent/path.dump')
  123. expect(notExists).toBe(false)
  124. })
  125. it('应该能够获取备份文件信息', async () => {
  126. const backupFile = await databaseBackup.createBackup()
  127. const info = await databaseBackup.getBackupInfo(backupFile)
  128. expect(info).toHaveProperty('size')
  129. expect(info).toHaveProperty('mtime')
  130. expect(info).toHaveProperty('formattedSize')
  131. expect(info.formattedSize).toBe('14 B') // 'mock backup data' 的长度
  132. })
  133. })
  134. describe('Scheduled Backups', () => {
  135. it('应该启动和停止定时备份', () => {
  136. const cron = require('node-cron')
  137. databaseBackup.startScheduledBackups()
  138. expect(cron.schedule).toHaveBeenCalled()
  139. databaseBackup.stopScheduledBackups()
  140. // 验证stop方法被调用
  141. expect(cron.schedule().stop).toHaveBeenCalled()
  142. })
  143. it('应该返回备份状态', () => {
  144. databaseBackup.startScheduledBackups()
  145. const status = databaseBackup.getBackupStatus()
  146. expect(status).toHaveProperty('scheduled')
  147. expect(status).toHaveProperty('nextRun')
  148. expect(status).toHaveProperty('lastRun')
  149. expect(status.scheduled).toBe(true)
  150. })
  151. })
  152. describe('Restore Integration', () => {
  153. it('应该能够找到最新备份', async () => {
  154. // 创建多个备份文件
  155. await databaseBackup.createBackup()
  156. await new Promise(resolve => setTimeout(resolve, 100)) // 确保时间戳不同
  157. await databaseBackup.createBackup()
  158. const latestBackup = await databaseRestore.findLatestBackup()
  159. expect(latestBackup).toBeDefined()
  160. expect(latestBackup).toContain('.dump')
  161. })
  162. it('应该能够列出所有备份', async () => {
  163. // 创建多个备份文件
  164. await databaseBackup.createBackup()
  165. await databaseBackup.createBackup()
  166. const backups = await databaseRestore.listBackups()
  167. expect(backups.length).toBe(2)
  168. expect(backups.every(b => b.endsWith('.dump'))).toBe(true)
  169. })
  170. })
  171. })