backup_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. package backup
  2. import (
  3. "os"
  4. "path/filepath"
  5. "testing"
  6. "github.com/0xJacky/Nginx-UI/settings"
  7. "github.com/stretchr/testify/assert"
  8. cosylogger "github.com/uozi-tech/cosy/logger"
  9. cosysettings "github.com/uozi-tech/cosy/settings"
  10. )
  11. func init() {
  12. // Initialize logging system to avoid nil pointer exceptions during tests
  13. cosylogger.Init("debug")
  14. // Clean up backup files at the start of tests
  15. cleanupBackupFiles()
  16. }
  17. // cleanupBackupFiles removes all backup files in the current directory
  18. func cleanupBackupFiles() {
  19. // Get current directory
  20. dir, err := os.Getwd()
  21. if err != nil {
  22. return
  23. }
  24. // Delete all backup files
  25. matches, err := filepath.Glob(filepath.Join(dir, "backup-*.zip"))
  26. if err == nil {
  27. for _, file := range matches {
  28. os.Remove(file)
  29. }
  30. }
  31. }
  32. // setupTestEnvironment creates a temporary environment for testing
  33. func setupTestEnvironment(t *testing.T) (string, func()) {
  34. // Create temporary test directory
  35. tempDir, err := os.MkdirTemp("", "backup-test-*")
  36. assert.NoError(t, err)
  37. // Set up necessary directories
  38. nginxDir := filepath.Join(tempDir, "nginx")
  39. nginxUIDir := filepath.Join(tempDir, "nginx-ui")
  40. configDir := filepath.Join(tempDir, "config")
  41. backupDir := filepath.Join(tempDir, "backup")
  42. // Create directories
  43. for _, dir := range []string{nginxDir, nginxUIDir, configDir, backupDir} {
  44. err = os.MkdirAll(dir, 0755)
  45. assert.NoError(t, err)
  46. }
  47. // Create some test files
  48. testFiles := map[string]string{
  49. filepath.Join(nginxDir, "nginx.conf"): "user nginx;\nworker_processes auto;\n",
  50. filepath.Join(nginxUIDir, "config.json"): `{"version": "1.0", "settings": {"theme": "dark"}}`,
  51. }
  52. for file, content := range testFiles {
  53. err = os.WriteFile(file, []byte(content), 0644)
  54. assert.NoError(t, err)
  55. }
  56. // Save original configuration
  57. origNginxConfigDir := settings.NginxSettings.ConfigDir
  58. origNginxUIConfigPath := cosysettings.ConfPath
  59. // Set test configuration
  60. settings.NginxSettings.ConfigDir = nginxDir
  61. cosysettings.ConfPath = filepath.Join(configDir, "config.ini")
  62. // Return cleanup function
  63. cleanup := func() {
  64. // Restore original configuration
  65. settings.NginxSettings.ConfigDir = origNginxConfigDir
  66. cosysettings.ConfPath = origNginxUIConfigPath
  67. // Delete temporary directory
  68. os.RemoveAll(tempDir)
  69. }
  70. return tempDir, cleanup
  71. }
  72. // Test backup and restore functionality
  73. func TestBackupAndRestore(t *testing.T) {
  74. // Make sure backup files are cleaned up at the start and end of the test
  75. cleanupBackupFiles()
  76. defer cleanupBackupFiles()
  77. // Create test configuration
  78. tempDir, err := os.MkdirTemp("", "nginx-ui-backup-test-*")
  79. assert.NoError(t, err)
  80. defer os.RemoveAll(tempDir)
  81. // Create config file
  82. configPath := filepath.Join(tempDir, "config.ini")
  83. testConfig := []byte("[app]\nName = Nginx UI Test\n")
  84. err = os.WriteFile(configPath, testConfig, 0644)
  85. assert.NoError(t, err)
  86. // Create database file
  87. dbName := settings.DatabaseSettings.GetName()
  88. dbFile := dbName + ".db"
  89. dbPath := filepath.Join(tempDir, dbFile)
  90. testDB := []byte("CREATE TABLE users (id INT, name TEXT);")
  91. err = os.WriteFile(dbPath, testDB, 0644)
  92. assert.NoError(t, err)
  93. // Create nginx directory
  94. nginxConfigDir := filepath.Join(tempDir, "nginx")
  95. err = os.MkdirAll(nginxConfigDir, 0755)
  96. assert.NoError(t, err)
  97. // Create test nginx config
  98. testNginxContent := []byte("server {\n listen 80;\n server_name example.com;\n}\n")
  99. err = os.WriteFile(filepath.Join(nginxConfigDir, "nginx.conf"), testNginxContent, 0644)
  100. assert.NoError(t, err)
  101. // Setup settings for testing
  102. originalConfPath := cosysettings.ConfPath
  103. originalNginxConfigDir := settings.NginxSettings.ConfigDir
  104. cosysettings.ConfPath = configPath
  105. settings.NginxSettings.ConfigDir = nginxConfigDir
  106. // Restore original settings after test
  107. defer func() {
  108. cosysettings.ConfPath = originalConfPath
  109. settings.NginxSettings.ConfigDir = originalNginxConfigDir
  110. }()
  111. // Run backup
  112. result, err := Backup()
  113. assert.NoError(t, err)
  114. assert.NotEmpty(t, result.BackupContent)
  115. assert.NotEmpty(t, result.BackupName)
  116. assert.NotEmpty(t, result.AESKey)
  117. assert.NotEmpty(t, result.AESIv)
  118. // Save backup content to a temporary file for restore testing
  119. backupPath := filepath.Join(tempDir, result.BackupName)
  120. err = os.WriteFile(backupPath, result.BackupContent, 0644)
  121. assert.NoError(t, err)
  122. // Test restore functionality
  123. restoreDir, err := os.MkdirTemp("", "nginx-ui-restore-test-*")
  124. assert.NoError(t, err)
  125. defer os.RemoveAll(restoreDir)
  126. // Decode AES key and IV
  127. aesKey, err := DecodeFromBase64(result.AESKey)
  128. assert.NoError(t, err)
  129. aesIv, err := DecodeFromBase64(result.AESIv)
  130. assert.NoError(t, err)
  131. // Perform restore
  132. restoreResult, err := Restore(RestoreOptions{
  133. BackupPath: backupPath,
  134. AESKey: aesKey,
  135. AESIv: aesIv,
  136. RestoreDir: restoreDir,
  137. RestoreNginx: true,
  138. RestoreNginxUI: true,
  139. VerifyHash: true,
  140. })
  141. assert.NoError(t, err)
  142. assert.NotEmpty(t, restoreResult.RestoreDir)
  143. // Verify restored directories
  144. nginxUIDir := filepath.Join(restoreDir, NginxUIDir)
  145. nginxDir := filepath.Join(restoreDir, NginxDir)
  146. _, err = os.Stat(nginxUIDir)
  147. assert.NoError(t, err)
  148. _, err = os.Stat(nginxDir)
  149. assert.NoError(t, err)
  150. // Verify hash info exists
  151. _, err = os.Stat(filepath.Join(restoreDir, HashInfoFile))
  152. assert.NoError(t, err)
  153. }
  154. // Test AES encryption/decryption
  155. func TestEncryptionDecryption(t *testing.T) {
  156. // Test data
  157. testData := []byte("This is a test message to encrypt and decrypt")
  158. // Create temp dir for testing
  159. testDir, err := os.MkdirTemp("", "nginx-ui-crypto-test-*")
  160. assert.NoError(t, err)
  161. defer os.RemoveAll(testDir)
  162. // Create test file
  163. testFile := filepath.Join(testDir, "test.txt")
  164. err = os.WriteFile(testFile, testData, 0644)
  165. assert.NoError(t, err)
  166. // Generate AES key and IV
  167. key, err := GenerateAESKey()
  168. assert.NoError(t, err)
  169. iv, err := GenerateIV()
  170. assert.NoError(t, err)
  171. // Test encrypt file
  172. err = encryptFile(testFile, key, iv)
  173. assert.NoError(t, err)
  174. // Read encrypted data
  175. encryptedData, err := os.ReadFile(testFile)
  176. assert.NoError(t, err)
  177. assert.NotEqual(t, string(testData), string(encryptedData))
  178. // Test decrypt file
  179. err = decryptFile(testFile, key, iv)
  180. assert.NoError(t, err)
  181. // Read decrypted data
  182. decryptedData, err := os.ReadFile(testFile)
  183. assert.NoError(t, err)
  184. assert.Equal(t, string(testData), string(decryptedData))
  185. }
  186. // Test AES direct encryption/decryption
  187. func TestAESEncryptDecrypt(t *testing.T) {
  188. // Generate key and IV
  189. key, err := GenerateAESKey()
  190. assert.NoError(t, err)
  191. iv, err := GenerateIV()
  192. assert.NoError(t, err)
  193. // Test data
  194. original := []byte("This is a test message for encryption and decryption")
  195. // Encrypt
  196. encrypted, err := AESEncrypt(original, key, iv)
  197. assert.NoError(t, err)
  198. assert.NotEqual(t, original, encrypted)
  199. // Decrypt
  200. decrypted, err := AESDecrypt(encrypted, key, iv)
  201. assert.NoError(t, err)
  202. assert.Equal(t, original, decrypted)
  203. }
  204. // Test Base64 encoding/decoding
  205. func TestEncodeDecodeBase64(t *testing.T) {
  206. original := []byte("Test data for base64 encoding")
  207. // Encode
  208. encoded := EncodeToBase64(original)
  209. // Decode
  210. decoded, err := DecodeFromBase64(encoded)
  211. assert.NoError(t, err)
  212. assert.Equal(t, original, decoded)
  213. }
  214. func TestGenerateAESKey(t *testing.T) {
  215. key, err := GenerateAESKey()
  216. assert.NoError(t, err)
  217. assert.Equal(t, 32, len(key))
  218. }
  219. func TestGenerateIV(t *testing.T) {
  220. iv, err := GenerateIV()
  221. assert.NoError(t, err)
  222. assert.Equal(t, 16, len(iv))
  223. }
  224. func TestEncryptDecryptFile(t *testing.T) {
  225. // Create temp directory
  226. tempDir, err := os.MkdirTemp("", "encrypt-file-test-*")
  227. assert.NoError(t, err)
  228. defer os.RemoveAll(tempDir)
  229. // Create test file
  230. testFile := filepath.Join(tempDir, "test.txt")
  231. testContent := []byte("This is test content for file encryption")
  232. err = os.WriteFile(testFile, testContent, 0644)
  233. assert.NoError(t, err)
  234. // Generate key and IV
  235. key, err := GenerateAESKey()
  236. assert.NoError(t, err)
  237. iv, err := GenerateIV()
  238. assert.NoError(t, err)
  239. // Encrypt file
  240. err = encryptFile(testFile, key, iv)
  241. assert.NoError(t, err)
  242. // Read encrypted content
  243. encryptedContent, err := os.ReadFile(testFile)
  244. assert.NoError(t, err)
  245. assert.NotEqual(t, testContent, encryptedContent)
  246. // Decrypt file
  247. err = decryptFile(testFile, key, iv)
  248. assert.NoError(t, err)
  249. // Read decrypted content
  250. decryptedContent, err := os.ReadFile(testFile)
  251. assert.NoError(t, err)
  252. assert.Equal(t, testContent, decryptedContent)
  253. }
  254. func TestBackupRestore(t *testing.T) {
  255. // Set up test environment
  256. tempDir, cleanup := setupTestEnvironment(t)
  257. defer cleanup()
  258. // Create a config.ini file since it's required for the test
  259. configDir := filepath.Join(tempDir, "config")
  260. configPath := filepath.Join(configDir, "config.ini")
  261. err := os.WriteFile(configPath, []byte("[app]\nName = Nginx UI Test\n"), 0644)
  262. assert.NoError(t, err)
  263. // Update Cosy settings path
  264. originalConfPath := cosysettings.ConfPath
  265. cosysettings.ConfPath = configPath
  266. defer func() {
  267. cosysettings.ConfPath = originalConfPath
  268. }()
  269. // Create backup
  270. backupResult, err := Backup()
  271. // If there's an error, log it but continue testing
  272. if err != nil {
  273. t.Logf("Backup failed with error: %v", err)
  274. t.Fail()
  275. return
  276. }
  277. assert.NotNil(t, backupResult.BackupContent)
  278. assert.NotEmpty(t, backupResult.BackupName)
  279. assert.NotEmpty(t, backupResult.AESKey)
  280. assert.NotEmpty(t, backupResult.AESIv)
  281. // Create temporary file for restore testing
  282. backupPath := filepath.Join(tempDir, backupResult.BackupName)
  283. err = os.WriteFile(backupPath, backupResult.BackupContent, 0644)
  284. assert.NoError(t, err)
  285. // Decode key and IV
  286. key, err := DecodeFromBase64(backupResult.AESKey)
  287. assert.NoError(t, err)
  288. iv, err := DecodeFromBase64(backupResult.AESIv)
  289. assert.NoError(t, err)
  290. // Create restore directory
  291. restoreDir := filepath.Join(tempDir, "restore")
  292. err = os.MkdirAll(restoreDir, 0755)
  293. assert.NoError(t, err)
  294. // Create restore options
  295. options := RestoreOptions{
  296. BackupPath: backupPath,
  297. AESKey: key,
  298. AESIv: iv,
  299. RestoreDir: restoreDir,
  300. VerifyHash: true,
  301. // Avoid modifying the system
  302. RestoreNginx: false,
  303. RestoreNginxUI: false,
  304. }
  305. // Test restore
  306. result, err := Restore(options)
  307. if err != nil {
  308. t.Logf("Restore failed with error: %v", err)
  309. t.Fail()
  310. return
  311. }
  312. assert.Equal(t, restoreDir, result.RestoreDir)
  313. // If hash verification is enabled, check the result
  314. if options.VerifyHash {
  315. assert.True(t, result.HashMatch, "Hash verification should pass")
  316. }
  317. }
  318. func TestCreateZipArchive(t *testing.T) {
  319. // Create temp directories
  320. tempSourceDir, err := os.MkdirTemp("", "zip-source-test-*")
  321. assert.NoError(t, err)
  322. defer os.RemoveAll(tempSourceDir)
  323. // Create some test files
  324. testFiles := []string{"file1.txt", "file2.txt", "subdir/file3.txt"}
  325. testContent := []byte("Test content")
  326. for _, file := range testFiles {
  327. filePath := filepath.Join(tempSourceDir, file)
  328. dirPath := filepath.Dir(filePath)
  329. err = os.MkdirAll(dirPath, 0755)
  330. assert.NoError(t, err)
  331. err = os.WriteFile(filePath, testContent, 0644)
  332. assert.NoError(t, err)
  333. }
  334. // Create zip file
  335. zipPath := filepath.Join(tempSourceDir, "test.zip")
  336. err = createZipArchive(zipPath, tempSourceDir)
  337. assert.NoError(t, err)
  338. // Verify zip file was created
  339. _, err = os.Stat(zipPath)
  340. assert.NoError(t, err)
  341. // Extract to new directory to verify contents
  342. extractDir := filepath.Join(tempSourceDir, "extract")
  343. err = os.MkdirAll(extractDir, 0755)
  344. assert.NoError(t, err)
  345. err = extractZipArchive(zipPath, extractDir)
  346. assert.NoError(t, err)
  347. // Verify extracted files
  348. for _, file := range testFiles {
  349. extractedPath := filepath.Join(extractDir, file)
  350. content, err := os.ReadFile(extractedPath)
  351. assert.NoError(t, err)
  352. assert.Equal(t, testContent, content)
  353. }
  354. }
  355. func TestHashCalculation(t *testing.T) {
  356. // Create temp file
  357. tempFile, err := os.CreateTemp("", "hash-test-*.txt")
  358. assert.NoError(t, err)
  359. defer os.Remove(tempFile.Name())
  360. // Write content
  361. testContent := []byte("Test content for hash calculation")
  362. _, err = tempFile.Write(testContent)
  363. assert.NoError(t, err)
  364. tempFile.Close()
  365. // Calculate hash
  366. hash, err := calculateFileHash(tempFile.Name())
  367. assert.NoError(t, err)
  368. assert.NotEmpty(t, hash)
  369. // Calculate again to verify consistency
  370. hash2, err := calculateFileHash(tempFile.Name())
  371. assert.NoError(t, err)
  372. assert.Equal(t, hash, hash2)
  373. // Modify file and check hash changes
  374. err = os.WriteFile(tempFile.Name(), []byte("Modified content"), 0644)
  375. assert.NoError(t, err)
  376. hash3, err := calculateFileHash(tempFile.Name())
  377. assert.NoError(t, err)
  378. assert.NotEqual(t, hash, hash3)
  379. }