package filesystem import ( "bytes" "context" "io" "path/filepath" "testing" "time" "github.com/bnema/gordon/internal/domain" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestBackupStorage_StoreAndGet(t *testing.T) { storage, err := NewBackupStorage(t.TempDir(), testLogger()) require.NoError(t, err) now := time.Date(2026, 2, 7, 11, 0, 0, 0, time.UTC) payload := []byte("backup-content") path, err := storage.Store(context.Background(), "app.example.com", "postgres", domain.ScheduleDaily, now, bytes.NewReader(payload)) require.NoError(t, err) rc, err := storage.Get(context.Background(), path) require.NoError(t, err) defer rc.Close() data, err := io.ReadAll(rc) require.NoError(t, err) assert.Equal(t, payload, data) } func TestBackupStorage_StoreSameSecondBackupsUseDistinctFinalAndTempPaths(t *testing.T) { rootDir := t.TempDir() storage, err := NewBackupStorage(rootDir, testLogger()) require.NoError(t, err) now := time.Date(2026, 2, 7, 11, 0, 0, 0, time.UTC) firstPayload := []byte("first-backup") secondPayload := []byte("second-backup") firstPath, err := storage.Store(context.Background(), "app.example.com", "postgres", domain.ScheduleDaily, now, bytes.NewReader(firstPayload)) require.NoError(t, err) secondPath, err := storage.Store(context.Background(), "app.example.com", "postgres", domain.ScheduleDaily, now, bytes.NewReader(secondPayload)) require.NoError(t, err) assert.NotEqual(t, firstPath, secondPath) assert.NoFileExists(t, firstPath+".tmp") assert.NoFileExists(t, secondPath+".tmp") firstRead, err := storage.Get(context.Background(), firstPath) require.NoError(t, err) defer firstRead.Close() firstData, err := io.ReadAll(firstRead) require.NoError(t, err) assert.Equal(t, firstPayload, firstData) secondRead, err := storage.Get(context.Background(), secondPath) require.NoError(t, err) defer secondRead.Close() secondData, err := io.ReadAll(secondRead) require.NoError(t, err) assert.Equal(t, secondPayload, secondData) } func TestBackupStorage_ListWithScheduleFilter(t *testing.T) { storage, err := NewBackupStorage(t.TempDir(), testLogger()) require.NoError(t, err) base := time.Date(2026, 2, 7, 10, 0, 0, 0, time.UTC) _, err = storage.Store(context.Background(), "app.example.com", "postgres", domain.ScheduleDaily, base, bytes.NewReader([]byte("d1"))) require.NoError(t, err) _, err = storage.Store(context.Background(), "app.example.com", "postgres", domain.ScheduleDaily, base.Add(time.Hour), bytes.NewReader([]byte("d2"))) require.NoError(t, err) _, err = storage.Store(context.Background(), "app.example.com", "postgres", domain.ScheduleWeekly, base.Add(2*time.Hour), bytes.NewReader([]byte("w1"))) require.NoError(t, err) schedule := domain.ScheduleDaily jobs, err := storage.List(context.Background(), "app.example.com", &schedule) require.NoError(t, err) require.Len(t, jobs, 2) assert.Equal(t, domain.ScheduleDaily, jobs[0].Schedule) assert.Equal(t, domain.ScheduleDaily, jobs[1].Schedule) assert.True(t, jobs[0].StartedAt.After(jobs[1].StartedAt)) } func TestBackupStorage_Delete(t *testing.T) { storage, err := NewBackupStorage(t.TempDir(), testLogger()) require.NoError(t, err) path, err := storage.Store(context.Background(), "app.example.com", "postgres", domain.ScheduleDaily, time.Now().UTC(), bytes.NewReader([]byte("data"))) require.NoError(t, err) err = storage.Delete(context.Background(), path) require.NoError(t, err) _, err = storage.Get(context.Background(), path) assert.Error(t, err) } func TestBackupStorage_ApplyRetention(t *testing.T) { storage, err := NewBackupStorage(t.TempDir(), testLogger()) require.NoError(t, err) base := time.Date(2026, 2, 7, 6, 0, 0, 0, time.UTC) for i := range 4 { _, err := storage.Store(context.Background(), "app.example.com", "postgres", domain.ScheduleDaily, base.Add(time.Duration(i)*time.Hour), bytes.NewReader([]byte("data"))) require.NoError(t, err) } deleted, err := storage.ApplyRetention(context.Background(), "app.example.com", domain.RetentionPolicy{Daily: 2}) require.NoError(t, err) assert.Equal(t, 2, deleted) schedule := domain.ScheduleDaily jobs, err := storage.List(context.Background(), "app.example.com", &schedule) require.NoError(t, err) assert.Len(t, jobs, 2) } func TestBackupStorage_StoreSanitizesDotOnlyPathComponents(t *testing.T) { storage, err := NewBackupStorage(t.TempDir(), testLogger()) require.NoError(t, err) path, err := storage.Store(context.Background(), "..", "...", domain.ScheduleDaily, time.Now().UTC(), bytes.NewReader([]byte("data"))) require.NoError(t, err) assert.NotContains(t, path, "..") } func TestBackupStorage_StoreSanitizesSchedulePathComponent(t *testing.T) { rootDir := t.TempDir() storage, err := NewBackupStorage(rootDir, testLogger()) require.NoError(t, err) path, err := storage.Store(context.Background(), "app.example.com", "postgres", domain.BackupSchedule("../../escape"), time.Now().UTC(), bytes.NewReader([]byte("data"))) require.NoError(t, err) rel, err := filepath.Rel(rootDir, path) require.NoError(t, err) assert.NotEqual(t, "..", rel) assert.NotContains(t, rel, "../") assert.NotContains(t, rel, "..\\") }