package vaultik_test import ( "context" "encoding/json" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/vaultik/internal/log" "sneak.berlin/go/vaultik/internal/storage/faultstore" "sneak.berlin/go/vaultik/internal/vaultik" ) // refuseBlobDeletes is an OnDelete hook that fails every blob delete. func refuseBlobDeletes(key string) bool { return strings.HasPrefix(key, "blobs/") } // TestPruneBlobs_FailedDeleteReturnsError checks that prune returns an // error, so the command exits non-zero, when an unreferenced blob cannot // be deleted. func TestPruneBlobs_FailedDeleteReturnsError(t *testing.T) { log.Initialize(log.Config{}) t.Parallel() env := newListEnv(t) // The store holds no manifest, so nothing references this blob. addBlob(t, env.store.testStorer, testBlobHashA) store := faultstore.New(env.store) store.OnDelete = refuseBlobDeletes env.v.Storage = store err := env.v.PruneBlobs(&vaultik.PruneOptions{Force: true}) require.ErrorContains(t, err, "1 blob(s) could not be deleted") } // TestPruneBlobs_JSONFailedDeleteReturnsError checks that prune --json // prints its document and then returns an error when an unreferenced // blob cannot be deleted. func TestPruneBlobs_JSONFailedDeleteReturnsError(t *testing.T) { log.Initialize(log.Config{}) t.Parallel() env := newListEnv(t) addBlob(t, env.store.testStorer, testBlobHashA) store := faultstore.New(env.store) store.OnDelete = refuseBlobDeletes env.v.Storage = store err := env.v.PruneBlobs(&vaultik.PruneOptions{JSON: true}) require.Error(t, err, "prune must fail when a blob cannot be deleted") var result vaultik.PruneBlobsResult require.NoError(t, json.Unmarshal(env.stdout.Bytes(), &result), "stdout must hold the JSON document, got:\n%s", env.stdout.String()) assert.Equal(t, 1, result.BlobsFailed) assert.Equal(t, 0, result.BlobsDeleted) } // TestCreateSnapshotPrune_FailedBlobDeleteReturnsError checks that the // prune step of snapshot create --prune fails the run when an // unreferenced blob cannot be deleted. func TestCreateSnapshotPrune_FailedBlobDeleteReturnsError(t *testing.T) { log.Initialize(log.Config{}) t.Parallel() ctx := context.Background() storeDir := filepath.Join(t.TempDir(), "store") v, _, _ := backUpToFileDestination(ctx, t, storeDir) // No snapshot references this blob, so the prune step deletes it. orphan := filepath.Join(storeDir, "blobs", testBlobHashA[:2], testBlobHashA[2:4], testBlobHashA) require.NoError(t, os.MkdirAll(filepath.Dir(orphan), 0o750)) require.NoError(t, os.WriteFile(orphan, []byte("orphan"), 0o600)) store := faultstore.New(v.Storage) store.OnDelete = refuseBlobDeletes v.Storage = store err := v.CreateSnapshot(&vaultik.SnapshotCreateOptions{ Cron: true, Prune: true, }) require.ErrorContains(t, err, "1 blob(s) could not be deleted") assert.FileExists(t, orphan) }