package services import ( "context" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" ) func setupTestBackupService(t *testing.T) (*BackupService, string, func()) { tmpDir, err := os.MkdirTemp("", "backup_test_*") require.NoError(t, err) config := BackupConfig{ BackupDir: tmpDir, RetentionDays: 7, DatabaseName: "test_db", DatabaseHost: "localhost", DatabasePort: "5432", DatabaseUser: "test_user", } logger := zap.NewNop() service, err := NewBackupService(config, logger) require.NoError(t, err) cleanup := func() { os.RemoveAll(tmpDir) } return service, tmpDir, cleanup } func TestBackupService_NewBackupService_Success(t *testing.T) { tmpDir, err := os.MkdirTemp("", "backup_test_*") require.NoError(t, err) defer os.RemoveAll(tmpDir) config := BackupConfig{ BackupDir: tmpDir, RetentionDays: 30, DatabaseName: "test_db", } logger := zap.NewNop() service, err := NewBackupService(config, logger) assert.NoError(t, err) assert.NotNil(t, service) assert.Equal(t, tmpDir, service.backupDir) assert.Equal(t, 30, service.retentionDays) assert.Equal(t, "test_db", service.databaseName) } func TestBackupService_NewBackupService_CreatesDirectory(t *testing.T) { tmpDir := filepath.Join(os.TempDir(), "backup_test_new_*") defer os.RemoveAll(tmpDir) config := BackupConfig{ BackupDir: tmpDir, RetentionDays: 30, DatabaseName: "test_db", } logger := zap.NewNop() service, err := NewBackupService(config, logger) assert.NoError(t, err) assert.NotNil(t, service) // Verify directory was created info, err := os.Stat(tmpDir) assert.NoError(t, err) assert.True(t, info.IsDir()) } func TestBackupService_NewBackupService_DefaultRetentionDays(t *testing.T) { tmpDir, err := os.MkdirTemp("", "backup_test_*") require.NoError(t, err) defer os.RemoveAll(tmpDir) config := BackupConfig{ BackupDir: tmpDir, RetentionDays: 0, // Should default to 30 DatabaseName: "test_db", } logger := zap.NewNop() service, err := NewBackupService(config, logger) assert.NoError(t, err) assert.NotNil(t, service) assert.Equal(t, 30, service.retentionDays) } func TestBackupService_NewBackupService_NegativeRetentionDays(t *testing.T) { tmpDir, err := os.MkdirTemp("", "backup_test_*") require.NoError(t, err) defer os.RemoveAll(tmpDir) config := BackupConfig{ BackupDir: tmpDir, RetentionDays: -5, // Should default to 30 DatabaseName: "test_db", } logger := zap.NewNop() service, err := NewBackupService(config, logger) assert.NoError(t, err) assert.NotNil(t, service) assert.Equal(t, 30, service.retentionDays) } func TestBackupService_CleanupOldBackups_NoBackups(t *testing.T) { service, _, cleanup := setupTestBackupService(t) defer cleanup() ctx := context.Background() err := service.CleanupOldBackups(ctx) assert.NoError(t, err) } func TestBackupService_CleanupOldBackups_DeletesOldBackups(t *testing.T) { service, backupDir, cleanup := setupTestBackupService(t) defer cleanup() ctx := context.Background() // Create old backup file (older than retention period) oldTime := time.Now().AddDate(0, 0, -10) // 10 days ago oldBackupPath := filepath.Join(backupDir, "test_db_20200101_000000.sql") oldFile, err := os.Create(oldBackupPath) require.NoError(t, err) oldFile.Close() // Set modification time to old time err = os.Chtimes(oldBackupPath, oldTime, oldTime) require.NoError(t, err) // Create recent backup file (within retention period) recentBackupPath := filepath.Join(backupDir, "test_db_20231228_120000.sql") recentFile, err := os.Create(recentBackupPath) require.NoError(t, err) recentFile.Close() // Run cleanup err = service.CleanupOldBackups(ctx) assert.NoError(t, err) // Verify old backup was deleted _, err = os.Stat(oldBackupPath) assert.True(t, os.IsNotExist(err)) // Verify recent backup still exists _, err = os.Stat(recentBackupPath) assert.NoError(t, err) } func TestBackupService_CleanupOldBackups_IgnoresNonBackupFiles(t *testing.T) { service, backupDir, cleanup := setupTestBackupService(t) defer cleanup() ctx := context.Background() // Create a non-backup file otherFile := filepath.Join(backupDir, "other_file.txt") err := os.WriteFile(otherFile, []byte("test"), 0644) require.NoError(t, err) // Run cleanup err = service.CleanupOldBackups(ctx) assert.NoError(t, err) // Verify non-backup file still exists _, err = os.Stat(otherFile) assert.NoError(t, err) } func TestBackupService_CleanupOldBackups_IgnoresDirectories(t *testing.T) { service, backupDir, cleanup := setupTestBackupService(t) defer cleanup() ctx := context.Background() // Create a subdirectory subDir := filepath.Join(backupDir, "subdir") err := os.MkdirAll(subDir, 0755) require.NoError(t, err) // Run cleanup err = service.CleanupOldBackups(ctx) assert.NoError(t, err) // Verify subdirectory still exists _, err = os.Stat(subDir) assert.NoError(t, err) } func TestBackupService_CleanupOldBackups_HandlesDumpFiles(t *testing.T) { service, backupDir, cleanup := setupTestBackupService(t) defer cleanup() ctx := context.Background() // Create old .dump file oldTime := time.Now().AddDate(0, 0, -10) oldBackupPath := filepath.Join(backupDir, "test_db_20200101_000000.dump") oldFile, err := os.Create(oldBackupPath) require.NoError(t, err) oldFile.Close() err = os.Chtimes(oldBackupPath, oldTime, oldTime) require.NoError(t, err) // Run cleanup err = service.CleanupOldBackups(ctx) assert.NoError(t, err) // Verify old .dump file was deleted _, err = os.Stat(oldBackupPath) assert.True(t, os.IsNotExist(err)) } func TestBackupService_ListBackups_Empty(t *testing.T) { service, _, cleanup := setupTestBackupService(t) defer cleanup() ctx := context.Background() backups, err := service.ListBackups(ctx) assert.NoError(t, err) if backups == nil { backups = []BackupInfo{} // Initialize empty slice if nil } assert.Len(t, backups, 0) } func TestBackupService_ListBackups_WithBackups(t *testing.T) { service, backupDir, cleanup := setupTestBackupService(t) defer cleanup() ctx := context.Background() // Create backup files backup1 := filepath.Join(backupDir, "test_db_20231228_120000.sql") err := os.WriteFile(backup1, []byte("backup1"), 0644) require.NoError(t, err) backup2 := filepath.Join(backupDir, "test_db_20231228_130000.dump") err = os.WriteFile(backup2, []byte("backup2"), 0644) require.NoError(t, err) // Create non-backup file (should be ignored) otherFile := filepath.Join(backupDir, "other.txt") err = os.WriteFile(otherFile, []byte("other"), 0644) require.NoError(t, err) // List backups backups, err := service.ListBackups(ctx) assert.NoError(t, err) assert.Len(t, backups, 2) // Verify backup files are listed backupNames := make(map[string]bool) for _, backup := range backups { backupNames[backup.FileName] = true assert.NotEmpty(t, backup.FilePath) assert.Greater(t, backup.Size, int64(0)) assert.NotZero(t, backup.CreatedAt) } assert.True(t, backupNames["test_db_20231228_120000.sql"]) assert.True(t, backupNames["test_db_20231228_130000.dump"]) } func TestBackupService_ListBackups_IgnoresDirectories(t *testing.T) { service, backupDir, cleanup := setupTestBackupService(t) defer cleanup() ctx := context.Background() // Create subdirectory subDir := filepath.Join(backupDir, "subdir") err := os.MkdirAll(subDir, 0755) require.NoError(t, err) // Create backup file backup1 := filepath.Join(backupDir, "test_db_20231228_120000.sql") err = os.WriteFile(backup1, []byte("backup1"), 0644) require.NoError(t, err) // List backups backups, err := service.ListBackups(ctx) assert.NoError(t, err) assert.Len(t, backups, 1) assert.Equal(t, "test_db_20231228_120000.sql", backups[0].FileName) } func TestBackupService_CreateBackup_RequiresPostgreSQL(t *testing.T) { // Skip this test as it requires PostgreSQL and pg_dump t.Skip("Skipping test that requires PostgreSQL pg_dump command") } func TestBackupService_BackupResult_Fields(t *testing.T) { result := &BackupResult{ Success: true, BackupPath: "/path/to/backup.sql", BackupSize: 1024, Duration: 5 * time.Second, ErrorMessage: "", CreatedAt: time.Now(), } assert.True(t, result.Success) assert.Equal(t, "/path/to/backup.sql", result.BackupPath) assert.Equal(t, int64(1024), result.BackupSize) assert.Equal(t, 5*time.Second, result.Duration) assert.Empty(t, result.ErrorMessage) assert.NotZero(t, result.CreatedAt) } func TestBackupService_BackupInfo_Fields(t *testing.T) { info := BackupInfo{ FileName: "test_db_20231228_120000.sql", FilePath: "/path/to/test_db_20231228_120000.sql", Size: 2048, CreatedAt: time.Now(), } assert.Equal(t, "test_db_20231228_120000.sql", info.FileName) assert.Equal(t, "/path/to/test_db_20231228_120000.sql", info.FilePath) assert.Equal(t, int64(2048), info.Size) assert.NotZero(t, info.CreatedAt) } func TestBackupService_BackupConfig_Fields(t *testing.T) { config := BackupConfig{ BackupDir: "/backups", RetentionDays: 30, DatabaseURL: "postgres://user:pass@localhost/db", DatabaseName: "test_db", DatabaseUser: "test_user", DatabaseHost: "localhost", DatabasePort: "5432", } assert.Equal(t, "/backups", config.BackupDir) assert.Equal(t, 30, config.RetentionDays) assert.Equal(t, "postgres://user:pass@localhost/db", config.DatabaseURL) assert.Equal(t, "test_db", config.DatabaseName) assert.Equal(t, "test_user", config.DatabaseUser) assert.Equal(t, "localhost", config.DatabaseHost) assert.Equal(t, "5432", config.DatabasePort) }