check / check (push) Failing after 3m40s
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. A successful nuke no longer advises prune for blobs it deletes itself. The faultstore test double gains an OnDelete hook. Judgement call: a snapshot nuke cannot remove from the local index also fails the run, since its metadata is then left on the store. 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.Error(t, err, "the prune step must fail the run")
|
|
assert.FileExists(t, orphan)
|
|
}
|