check / check (push) Waiting to run
prune, and the prune step of snapshot create --prune, counted a blob it could not delete and still exited 0. It now prints its summary or --json document and then returns an error, which replaces the "Failed to delete N blob(s)" line. remote nuke only warned when a snapshot's metadata could not be deleted, advised vaultik prune, which never removes metadata, and then reported the store empty. It now stops with an error, does not report the store empty, and says to run vaultik remote nuke --force again. It checks that the local index belongs to the configured destination before deleting anything, and that error carries no such advice. A successful nuke no longer advises prune. Model: opus-5-5
99 lines
2.9 KiB
Go
99 lines
2.9 KiB
Go
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)
|
|
}
|