package cli //nolint:testpackage // shares runEntry and the hermetic config helpers import ( "context" "database/sql" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/vaultik/internal/database" "sneak.berlin/go/vaultik/internal/snapshot" "sneak.berlin/go/vaultik/internal/types" ) // The `snapshot purge` command word and flags used below. const ( cmdPurge = "purge" flagKeepLatest = "--keep-latest" flagForce = "--force" ) // TestEntrySnapshotPurgeKeepsMetadataWhenLocalIndexDeleteFails makes // removing a snapshot from the local index fail 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 // anyway and exit 0. // //nolint:paralleltest // replaces os.Args, os.Stdout, os.Stderr and the xdg globals func TestEntrySnapshotPurgeKeepsMetadataWhenLocalIndexDeleteFails(t *testing.T) { ctx := context.Background() configPath := writeHermeticPruneConfig(t, false) // writeHermeticPruneConfig puts the index and the store directory // beside the config file. dir := filepath.Dir(configPath) // Two snapshots of the same name: --keep-latest purges the older. olderStart := time.Date(2026, 5, 1, 9, 0, 0, 0, time.UTC) newerStart := olderStart.Add(time.Hour) olderID := "test-host_test_" + olderStart.Format(time.RFC3339) newerID := "test-host_test_" + newerStart.Format(time.RFC3339) db, err := database.New(ctx, filepath.Join(dir, "index.sqlite")) require.NoError(t, err) repos := database.NewRepositories(db) manifestPaths := make(map[string]string) for id, startedAt := range map[string]time.Time{ olderID: olderStart, newerID: newerStart, } { completedAt := startedAt.Add(time.Minute) snap := &database.Snapshot{ ID: types.SnapshotID(id), Hostname: "test-host", VaultikVersion: "test", StartedAt: startedAt, CompletedAt: &completedAt, } err = repos.WithTx(ctx, func(ctx context.Context, tx *sql.Tx) error { return repos.Snapshots.Create(ctx, tx, snap) }) require.NoError(t, err) manifestPaths[id] = filepath.Join(dir, "store", "metadata", snapshot.RemoteSnapshotKey(id), "manifest.json.zst") require.NoError(t, os.MkdirAll(filepath.Dir(manifestPaths[id]), 0o750)) require.NoError(t, os.WriteFile(manifestPaths[id], []byte("stub"), 0o600)) } _, 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, flagKeepLatest, flagForce) assert.Equal(t, 1, code) assert.Contains(t, stderr, "simulated index error") assert.FileExists(t, manifestPaths[olderID], "a snapshot still in the local index keeps its metadata") }