package vaultik //nolint:testpackage // exercises unexported count helpers import ( "bytes" "context" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "sneak.berlin/go/vaultik/internal/database" "sneak.berlin/go/vaultik/internal/log" "sneak.berlin/go/vaultik/internal/snapshot" "sneak.berlin/go/vaultik/internal/ui" ) // TestTableCountForReportSurfacesReadFailure is the regression guard for // the discarded-error bug: getTableCount for a table its query cannot // resolve must not silently become 0. A count that could not be read is // reported as unknown, which a reader can tell apart from an empty table. func TestTableCountForReportSurfacesReadFailure(t *testing.T) { log.Initialize(log.Config{}) t.Parallel() ctx := context.Background() db, err := database.New(ctx, ":memory:") require.NoError(t, err) t.Cleanup(func() { _ = db.Close() }) v := &Vaultik{DB: db} v.SetContext(ctx) // A table present in the schema reads as a real count. blobs := v.tableCountForReport("blobs") require.NotNil(t, blobs, "an existing table must read as a real count") assert.Equal(t, int64(0), *blobs) // A syntactically valid name the sanitizer accepts but whose table // the query cannot resolve is the exact shape #96 describes: a // would-be loud failure that used to be discarded into a 0. _, err = v.getTableCount("snapshots_missing") require.Error(t, err, "a query against a nonexistent table must fail") missing := v.tableCountForReport("snapshots_missing") assert.Nil(t, missing, "a failed read is unknown, not a count") // The rendered count for a failed read must say unknown, never 0. assert.Equal(t, unknownText, countText(missing)) assert.NotEqual(t, "0", countText(missing)) } // TestCountTextDistinguishesEmptyFromUnknown pins the distinction the // output has to preserve: 0 means the table was empty, "unknown" means // the count could not be read. func TestCountTextDistinguishesEmptyFromUnknown(t *testing.T) { t.Parallel() zero := int64(0) seven := int64(7) assert.Equal(t, "0", countText(&zero)) assert.Equal(t, "7", countText(&seven)) assert.Equal(t, unknownText, countText(nil)) } // TestCountDiffUnknownWhenEitherSideUnknown checks that a delta computed // from an unreadable count is itself unknown rather than a plausible // number. func TestCountDiffUnknownWhenEitherSideUnknown(t *testing.T) { t.Parallel() before := int64(10) after := int64(3) require.NotNil(t, countDiff(&before, &after)) assert.Equal(t, int64(7), *countDiff(&before, &after)) assert.Nil(t, countDiff(nil, &after), "unknown before yields unknown delta") assert.Nil(t, countDiff(&before, nil), "unknown after yields unknown delta") assert.Nil(t, countDiff(nil, nil)) } // TestPruneDatabaseSummaryLabelsTotals removes the only incomplete // snapshot from an index that also holds a complete one. The figure in // brackets is every snapshot left in the local index, so the line must // say so rather than read as incomplete snapshots that remain. func TestPruneDatabaseSummaryLabelsTotals(t *testing.T) { log.Initialize(log.Config{}) t.Parallel() ctx := context.Background() db, err := database.New(ctx, ":memory:") require.NoError(t, err) t.Cleanup(func() { _ = db.Close() }) repos := database.NewRepositories(db) startedAt := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC) completedAt := startedAt.Add(time.Minute) require.NoError(t, repos.Snapshots.Create(ctx, nil, &database.Snapshot{ ID: "host_complete_2026-01-02T03:04:05Z", Hostname: "host", StartedAt: startedAt, CompletedAt: &completedAt, })) require.NoError(t, repos.Snapshots.Create(ctx, nil, &database.Snapshot{ ID: "host_incomplete_2026-01-02T03:04:05Z", Hostname: "host", StartedAt: startedAt, })) var out bytes.Buffer v := &Vaultik{ DB: db, Repositories: repos, SnapshotManager: snapshot.NewSnapshotManager( snapshot.SnapshotManagerParams{Repos: repos}), UI: ui.NewWithColor(&out, false), } v.SetContext(ctx) result, err := v.PruneDatabase() require.NoError(t, err) require.Equal(t, int64(1), result.SnapshotsDeleted) assert.Contains(t, out.String(), "Incomplete snapshots: 1 removed "+ "(total snapshots left in the local index: 1).") assert.Contains(t, out.String(), "Orphaned files: 0 removed "+ "(total files left in the local index: 0).") assert.Contains(t, out.String(), "Orphaned chunks: 0 removed "+ "(total chunks left in the local index: 0).") assert.Contains(t, out.String(), "Orphaned blobs: 0 removed "+ "(total blobs left in the local index: 0).") }