package vaultik //nolint:testpackage // inspects unexported restore plan internals import ( "context" "math" "testing" "github.com/stretchr/testify/require" "sneak.berlin/go/vaultik/internal/database" "sneak.berlin/go/vaultik/internal/types" ) // TestPickNextDownloadReturnsNilUUIDFile proves a genuine pending file // carrying the nil UUID is picked for download rather than mistaken for // "nothing left" — the bug that could abandon every remaining file. func TestPickNextDownloadReturnsNilUUIDFile(t *testing.T) { t.Parallel() var nilID types.FileID // zero value is the nil UUID plan := &restorePlan{ fileBlobs: map[types.FileID]map[string]struct{}{ nilID: {"blobhash": {}}, }, } id, ok := plan.pickNextDownload() require.True(t, ok, "pickNextDownload treated a pending nil-UUID file as nothing to do") require.True(t, id.IsZero(), "expected the nil-UUID file to be picked") } // TestPickNextDownloadEmptyPlan confirms the second return value is false // only when no file needs a download. func TestPickNextDownloadEmptyPlan(t *testing.T) { t.Parallel() plan := &restorePlan{ fileBlobs: map[types.FileID]map[string]struct{}{}, } _, ok := plan.pickNextDownload() require.False(t, ok, "pickNextDownload reported work on an empty plan") } // TestRunRestoreLoopFailsOnAbandonedFiles proves the loop returns an // error rather than silent success when files remain pending after it // can make no further progress. func TestRunRestoreLoopFailsOnAbandonedFiles(t *testing.T) { t.Parallel() ctx := context.Background() db, err := database.NewTestDB() require.NoError(t, err) t.Cleanup(func() { _ = db.Close() }) repos := database.NewRepositories(db) cache, err := newBlobDiskCache(math.MaxInt64) require.NoError(t, err) t.Cleanup(func() { _ = cache.Close() }) v := &Vaultik{ctx: ctx} session := &restoreSession{ v: v, ctx: ctx, repos: repos, sweeper: newRestoreSweeper(ctx, repos, cache, 1), result: &RestoreResult{}, } // A file that is still pending but whose uncached-blob set is empty // and which was never queued as ready: the loop can neither restore // nor download it. This is the abandonment the guard must catch. var stuck types.FileID plan := &restorePlan{ fileBlobs: map[types.FileID]map[string]struct{}{stuck: {}}, blobFiles: map[string]map[types.FileID]struct{}{}, cached: map[string]struct{}{}, } err = v.runRestoreLoop(session, plan, map[types.FileID]*database.File{}, 0) require.ErrorIs(t, err, errRestoreIncomplete) }