package cli //nolint:testpackage // shares runEntry and the hermetic config helpers import ( "context" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/vaultik/internal/database" "sneak.berlin/go/vaultik/internal/snapshot" ) // Words and flags of `snapshot purge` used to build argument vectors // below. const ( cmdPurge = "purge" flagOlderThan = "--older-than" flagForce = "--force" ) // TestEntrySnapshotPurgeKeepsMetadataWhenLocalIndexDeleteFails makes // the local index refuse to delete an old snapshot and checks that // `snapshot purge` exits non-zero and leaves that snapshot's metadata on // the destination store. It used to log the error, delete the metadata // and exit 0. // // `snapshot create --prune` runs the same purge, but cannot be driven // this way: the refusal also breaks the backup's metadata export, which // deletes the other snapshots from its copy of the local index. // //nolint:paralleltest // replaces os.Args, os.Stdout, os.Stderr and the xdg globals func TestEntrySnapshotPurgeKeepsMetadataWhenLocalIndexDeleteFails(t *testing.T) { configPath := writeHermeticPruneConfig(t, false) dir := filepath.Dir(configPath) indexPath := filepath.Join(dir, "index.sqlite") // seedStaleSnapshotRecord writes an old snapshot to the local index // only; the manifest planted here puts it on the destination store // too, so the purge selects it rather than dropping it as stale. seedStaleSnapshotRecord(t, indexPath) // While the local index holds a snapshot, purge fails on a // destination without the marker a backup writes. require.NoError(t, os.WriteFile( filepath.Join(dir, "store", "vaultik-destination.txt"), nil, 0o600)) manifestDir := filepath.Join(dir, "store", "metadata", snapshot.RemoteSnapshotKey(stalePruneSnapshotID)) require.NoError(t, os.MkdirAll(manifestDir, 0o750)) manifestPath := filepath.Join(manifestDir, "manifest.json.zst") require.NoError(t, os.WriteFile(manifestPath, []byte("stub"), 0o600)) ctx := context.Background() db, err := database.New(ctx, indexPath) require.NoError(t, err) _, err = db.Conn().ExecContext(ctx, ` CREATE TRIGGER refuse_snapshot_delete BEFORE DELETE ON snapshots BEGIN SELECT RAISE(ABORT, 'simulated index error'); END`) require.NoError(t, err) require.NoError(t, db.Close()) code, _, stderr := runEntry(t, flagConfig, configPath, cmdSnapshot, cmdPurge, flagOlderThan, "30d", flagForce) assert.Equal(t, 1, code) assert.Contains(t, stderr, "simulated index error", "the purge must fail at the local-index delete") assert.FileExists(t, manifestPath, "the snapshot is still in the local index, so its metadata must stay") }