restore.test.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
  2. import { DatabaseRestore } from '../restore'
  3. import { promises as fs } from 'fs'
  4. import path from 'path'
  5. // Mock pg-dump-restore
  6. vi.mock('pg-dump-restore', () => ({
  7. pgRestore: vi.fn().mockResolvedValue(undefined),
  8. }))
  9. // Mock fs with importOriginal for partial mocking
  10. vi.mock('fs', async (importOriginal) => {
  11. const actual = await importOriginal()
  12. return {
  13. ...actual,
  14. promises: {
  15. ...actual.promises,
  16. readdir: vi.fn().mockResolvedValue([]),
  17. access: vi.fn().mockResolvedValue(undefined),
  18. stat: vi.fn().mockResolvedValue({ size: 1024, mtime: new Date() }),
  19. },
  20. }
  21. })
  22. // Mock logger
  23. vi.mock('../logger', () => ({
  24. logger: {
  25. db: vi.fn(),
  26. error: vi.fn(),
  27. },
  28. }))
  29. describe('DatabaseRestore', () => {
  30. let restore: DatabaseRestore
  31. beforeEach(() => {
  32. vi.clearAllMocks()
  33. restore = new DatabaseRestore()
  34. })
  35. afterEach(() => {
  36. vi.restoreAllMocks()
  37. })
  38. describe('getDbConfig', () => {
  39. it('应该返回正确的数据库配置', () => {
  40. process.env.DB_HOST = 'test-host'
  41. process.env.DB_PORT = '5433'
  42. process.env.DB_DATABASE = 'test-db'
  43. process.env.DB_USERNAME = 'test-user'
  44. process.env.DB_PASSWORD = 'test-password'
  45. const config = (restore as any).getDbConfig()
  46. expect(config).toEqual({
  47. host: 'test-host',
  48. port: 5433,
  49. database: 'test-db',
  50. username: 'test-user',
  51. password: 'test-password',
  52. })
  53. })
  54. it('应该使用默认值当环境变量未设置时', () => {
  55. delete process.env.DB_HOST
  56. delete process.env.DB_PORT
  57. delete process.env.DB_DATABASE
  58. delete process.env.DB_USERNAME
  59. delete process.env.DB_PASSWORD
  60. const config = (restore as any).getDbConfig()
  61. expect(config).toEqual({
  62. host: 'localhost',
  63. port: 5432,
  64. database: 'postgres',
  65. username: 'postgres',
  66. password: '',
  67. })
  68. })
  69. })
  70. describe('findLatestBackup', () => {
  71. it('应该返回最新的备份文件', async () => {
  72. const { readdir } = await import('fs')
  73. vi.mocked(readdir).mockResolvedValue([
  74. 'backup-2024-01-01T00-00-00Z.dump',
  75. 'backup-2024-01-03T00-00-00Z.dump',
  76. 'backup-2024-01-02T00-00-00Z.dump',
  77. ])
  78. const latest = await restore.findLatestBackup()
  79. expect(latest).toBe(path.join('./backups', 'backup-2024-01-03T00-00-00Z.dump'))
  80. })
  81. it('应该返回null当没有备份文件时', async () => {
  82. const { readdir } = await import('fs')
  83. vi.mocked(readdir).mockResolvedValue(['some-other-file.txt'])
  84. const latest = await restore.findLatestBackup()
  85. expect(latest).toBeNull()
  86. })
  87. it('应该在读取目录失败时返回null', async () => {
  88. const { readdir } = await import('fs')
  89. const { logger } = await import('../logger')
  90. vi.mocked(readdir).mockRejectedValueOnce(new Error('读取目录失败'))
  91. const latest = await restore.findLatestBackup()
  92. expect(latest).toBeNull()
  93. expect(logger.error).toHaveBeenCalled()
  94. })
  95. })
  96. describe('listBackups', () => {
  97. it('应该返回所有备份文件列表', async () => {
  98. const { readdir } = await import('fs')
  99. vi.mocked(readdir).mockResolvedValue([
  100. 'backup-2024-01-01.dump',
  101. 'some-other-file.txt',
  102. 'backup-2024-01-02.dump',
  103. ])
  104. const backups = await restore.listBackups()
  105. expect(backups).toEqual([
  106. 'backup-2024-01-01.dump',
  107. 'backup-2024-01-02.dump',
  108. ])
  109. })
  110. it('应该在读取目录失败时返回空数组', async () => {
  111. const { readdir } = await import('fs')
  112. const { logger } = await import('../logger')
  113. vi.mocked(readdir).mockRejectedValueOnce(new Error('读取目录失败'))
  114. const backups = await restore.listBackups()
  115. expect(backups).toEqual([])
  116. expect(logger.error).toHaveBeenCalled()
  117. })
  118. })
  119. describe('backupExists', () => {
  120. it('应该返回true当备份文件存在时', async () => {
  121. const { access } = await import('fs')
  122. const exists = await restore.backupExists('/path/to/backup.dump')
  123. expect(exists).toBe(true)
  124. expect(access).toHaveBeenCalledWith('/path/to/backup.dump')
  125. })
  126. it('应该返回false当备份文件不存在时', async () => {
  127. const { access } = await import('fs')
  128. vi.mocked(access).mockRejectedValueOnce(new Error('文件不存在'))
  129. const exists = await restore.backupExists('/path/to/backup.dump')
  130. expect(exists).toBe(false)
  131. })
  132. })
  133. describe('getBackupInfo', () => {
  134. it('应该返回备份文件信息', async () => {
  135. const { stat } = await import('fs')
  136. const testDate = new Date()
  137. vi.mocked(stat).mockResolvedValueOnce({
  138. size: 1048576,
  139. mtime: testDate,
  140. } as any)
  141. const info = await restore.getBackupInfo('/path/to/backup.dump')
  142. expect(info).toEqual({
  143. size: 1048576,
  144. mtime: testDate,
  145. formattedSize: '1 MB',
  146. })
  147. })
  148. it('应该在获取信息失败时抛出错误', async () => {
  149. const { stat } = await import('fs')
  150. vi.mocked(stat).mockRejectedValueOnce(new Error('获取文件信息失败'))
  151. await expect(restore.getBackupInfo('/path/to/backup.dump')).rejects.toThrow('获取备份信息失败')
  152. })
  153. })
  154. describe('formatFileSize', () => {
  155. it('应该正确格式化文件大小', () => {
  156. const formatFileSize = (restore as any).formatFileSize
  157. expect(formatFileSize(0)).toBe('0 B')
  158. expect(formatFileSize(1024)).toBe('1 KB')
  159. expect(formatFileSize(1048576)).toBe('1 MB')
  160. expect(formatFileSize(1073741824)).toBe('1 GB')
  161. })
  162. })
  163. })