Ctrl K
ComunidadGuías…

internal/store/history_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package store

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"slices"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"
)

const migrationsInV1 = 7

const p1 = "https://10.0.0.5:6443"

const p2 = "https://10.0.0.6:6443"

var noon = time.Date(2026, 8, 29, 12, 0, 0, 0, time.UTC)

func dbPath(t *testing.T) string {
	t.Helper()
	return filepath.Join(t.TempDir(), "spinoza", "history.db")
}

func openHistory(t *testing.T, path string) *Store {
	t.Helper()
	store, err := Open(t.Context(), path)
	if err != nil {
		t.Fatalf("open: %v", err)
	}
	t.Cleanup(func() { _ = store.Close() })
	return store
}

func entry(cluster string, at time.Time, name string) Entry {
	return Entry{
		Cluster:   cluster,
		At:        at,
		Verb:      "delete",
		Actor:     "[email protected]",
		Group:     "apps",
		Version:   "v1",
		Resource:  "deployments",
		Kind:      "Deployment",
		Namespace: "default",
		Name:      name,
		Detail:    "deleted the deployment",
		Outcome:   "ok",
		Message:   "",
	}
}

func record(t *testing.T, store *Store, held Entry) {
	t.Helper()
	if err := store.For(held.Cluster).Record(t.Context(), held); err != nil {
		t.Fatalf("record: %v", err)
	}
}

func recent(t *testing.T, store *Store, query Query) Page {
	t.Helper()
	page, err := store.Recent(t.Context(), query)
	if err != nil {
		t.Fatalf("recent: %v", err)
	}
	return page
}

func names(page Page) []string {
	out := make([]string, 0, len(page.Entries))
	for _, held := range page.Entries {
		out = append(out, held.Name)
	}
	return out
}

func pragma(t *testing.T, store *Store, name string) string {
	t.Helper()
	var value string
	err := store.reads.QueryRowContext(t.Context(), "PRAGMA "+name).Scan(&value)
	if err != nil {
		t.Fatalf("pragma %s: %v", name, err)
	}
	return value
}

func TestTheDefaultPathSitsBesideTheOtherStores(t *testing.T) {
	path, err := DefaultPath()
	if err != nil {
		t.Skipf("this platform names no config directory: %v", err)
	}
	if filepath.Base(path) != "history.db" {
		t.Fatalf("path = %q, want it to end in history.db", path)
	}
	if filepath.Base(filepath.Dir(path)) != "spinoza" {
		t.Fatalf("path = %q, want it beside settings.json and protected.json", path)
	}
}

func TestTheDefaultPathNeedsAConfigDirectory(t *testing.T) {
	t.Setenv("HOME", "")
	t.Setenv("XDG_CONFIG_HOME", "")

	_, err := DefaultPath()

	if err == nil {
		t.Skip("this platform still names a config directory without HOME")
	}
	if !strings.Contains(err.Error(), "store") {
		t.Fatalf("error = %q, want it to say what it was doing", err.Error())
	}
}

func TestTheDatabaseIsOpenedInWalMode(t *testing.T) {
	store := openHistory(t, dbPath(t))

	if mode := pragma(t, store, "journal_mode"); !strings.EqualFold(mode, "wal") {
		t.Fatalf("journal_mode = %q, want wal; two spinoza windows share this file", mode)
	}
}

func TestAWriterWaitsForAnotherRatherThanFailing(t *testing.T) {
	store := openHistory(t, dbPath(t))

	if timeout := pragma(t, store, "busy_timeout"); timeout != "5000" {
		t.Fatalf("busy_timeout = %q, want 5000; without it a second window's write fails outright", timeout)
	}
}

func TestAWorkingStoreHasNothingToApologiseFor(t *testing.T) {
	if reason := openHistory(t, dbPath(t)).Reason(); reason != "" {
		t.Fatalf("reason = %q, want none from a healthy store", reason)
	}
}

func TestAStoreWithNowhereToWriteStillWorks(t *testing.T) {
	store, err := Open(t.Context(), "")
	if err != nil {
		t.Fatalf("open: %v, want a degraded store rather than a failure", err)
	}
	if store.Reason() == "" {
		t.Fatal("the store did not say why it is not recording")
	}
	record(t, store, entry(p1, noon, "web"))
	if len(recent(t, store, Query{}).Entries) != 0 {
		t.Fatal("a store with no database returned rows")
	}
	if forgetErr := store.Forget(t.Context(), ""); forgetErr != nil {
		t.Fatalf("forget: %v", forgetErr)
	}
	if closeErr := store.Close(); closeErr != nil {
		t.Fatalf("close: %v", closeErr)
	}
}

func TestAReadOnlyDirectoryDoesNotStopSpinoza(t *testing.T) {
	root := t.TempDir()
	if err := os.Chmod(root, 0o500); err != nil {
		t.Fatalf("chmod: %v", err)
	}
	t.Cleanup(func() { _ = os.Chmod(root, 0o700) })

	store, err := Open(t.Context(), filepath.Join(root, "spinoza", "history.db"))

	if err == nil {
		t.Skip("this filesystem let the write through")
	}
	if store == nil {
		t.Fatal("Open returned no store; spinoza must still start without history")
	}
	if !strings.Contains(store.Reason(), "history.db") {
		t.Fatalf("reason = %q, want it to name the file it could not use", store.Reason())
	}
	record(t, store, entry(p1, noon, "web"))
}

func TestACorruptFileDoesNotStopSpinoza(t *testing.T) {
	path := dbPath(t)
	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		t.Fatalf("mkdir: %v", err)
	}
	if err := os.WriteFile(path, []byte("this is not a database"), 0o600); err != nil {
		t.Fatalf("write: %v", err)
	}

	store, err := Open(t.Context(), path)

	if err == nil {
		t.Fatal("a corrupt file opened cleanly")
	}
	if store.Reason() == "" {
		t.Fatal("the store did not say why it is not recording")
	}
	record(t, store, entry(p1, noon, "web"))
	if len(recent(t, store, Query{}).Entries) != 0 {
		t.Fatal("a corrupt store returned rows")
	}
}

func TestAFileFromANewerSpinozaIsRefusedRatherThanMigrated(t *testing.T) {
	path := dbPath(t)
	store := openHistory(t, path)
	if _, err := store.writes.ExecContext(t.Context(), "PRAGMA user_version = 99"); err != nil {
		t.Fatalf("bump: %v", err)
	}
	if err := store.Close(); err != nil {
		t.Fatalf("close: %v", err)
	}

	reopened, reopenErr := Open(t.Context(), path)

	if !errors.Is(reopenErr, errFromTheFuture) {
		t.Fatalf("error = %v, want it to refuse a newer schema rather than migrate over it", reopenErr)
	}
	if !strings.Contains(reopened.Reason(), "newer spinoza") {
		t.Fatalf("reason = %q, want it to say the file is from a newer spinoza", reopened.Reason())
	}
}

func TestAFileWithAnAdditiveMigrationAheadIsStillOpened(t *testing.T) {
	path := dbPath(t)
	store := openHistory(t, path)
	tabs := store.Tabs()
	if err := tabs.Remember(t.Context(), Tab{ID: p1, Context: "p-mk1", Seen: noon}); err != nil {
		t.Fatalf("remember: %v", err)
	}
	if err := tabs.Recording(t.Context(), p1, "workloads"); err != nil {
		t.Fatalf("recording: %v", err)
	}
	if err := store.Close(); err != nil {
		t.Fatalf("close: %v", err)
	}
	saved := migrations
	migrations = slices.Clone(saved[:migrationsInV1])
	t.Cleanup(func() { migrations = saved })

	reopened, reopenErr := Open(t.Context(), path)
	t.Cleanup(func() { _ = reopened.Close() })

	if reopenErr != nil {
		t.Fatalf("error = %v, want v1.30.0 to open a file this spinoza wrote", reopenErr)
	}
	if reopened.Reason() != "" {
		t.Fatalf("reason = %q, want an older spinoza to keep recording", reopened.Reason())
	}
	found, readErr := reopened.Tabs().All(t.Context())
	if readErr != nil {
		t.Fatalf("all: %v", readErr)
	}
	if len(found) != 1 || found[0].Timeline != "workloads" {
		t.Fatalf("the tab came back as %+v, want what it recorded before the upgrade", found)
	}
}

func TestAFileStampedBeforeTheFloorWasKeptIsRepaired(t *testing.T) {
	path := dbPath(t)
	store := openHistory(t, path)
	if _, err := store.writes.ExecContext(t.Context(), "DROP TABLE schema_progress"); err != nil {
		t.Fatalf("drop: %v", err)
	}
	bump := fmt.Sprintf("PRAGMA user_version = %d", len(migrations))
	if _, err := store.writes.ExecContext(t.Context(), bump); err != nil {
		t.Fatalf("bump: %v", err)
	}
	if err := store.Close(); err != nil {
		t.Fatalf("close: %v", err)
	}

	reopened := openHistory(t, path)

	var floor int
	if err := reopened.writes.QueryRowContext(t.Context(), "PRAGMA user_version").Scan(&floor); err != nil {
		t.Fatalf("floor: %v", err)
	}
	if floor != readableFrom {
		t.Fatalf("floor = %d, want it lowered to %d so an older spinoza can still read the file", floor, readableFrom)
	}
	var applied int
	if err := reopened.writes.QueryRowContext(t.Context(), selectProgress).Scan(&applied); err != nil {
		t.Fatalf("progress: %v", err)
	}
	if applied != len(migrations) {
		t.Fatalf("applied = %d, want all %d migrations counted", applied, len(migrations))
	}
}

func TestTheReadableFloorNamesAMigrationThatExists(t *testing.T) {
	if readableFrom < 1 || readableFrom > len(migrations) {
		t.Fatalf("readableFrom = %d, want it between 1 and %d, or no file will open", readableFrom, len(migrations))
	}
}

func TestASchemaThatWillNotApplyIsReported(t *testing.T) {
	saved := migrations
	migrations = []string{"CREATE TABLE ("}
	t.Cleanup(func() { migrations = saved })

	store, err := Open(t.Context(), dbPath(t))

	if err == nil {
		t.Fatal("a broken migration was accepted")
	}
	if store.Reason() == "" {
		t.Fatal("the store did not say why it is not recording")
	}
}

func TestAnAbsentLimitFallsBackToTheDefault(t *testing.T) {
	store := openHistory(t, dbPath(t))
	for at := range defaultLimit + 5 {
		record(t, store, entry(p1, noon.Add(time.Duration(at)*time.Second), "one"))
	}

	page := recent(t, store, Query{})

	if len(page.Entries) != defaultLimit {
		t.Fatalf("returned %d entries with no limit asked for, want the default %d", len(page.Entries), defaultLimit)
	}
}

func TestAnEnormousLimitIsClampedRatherThanObeyed(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "web"))

	page, err := store.Recent(t.Context(), Query{Limit: maxLimit * 100})
	if err != nil {
		t.Fatalf("recent: %v", err)
	}
	if len(page.Entries) != 1 {
		t.Fatalf("returned %d entries, want the one that exists", len(page.Entries))
	}
}

func TestWhatWasDoneComesBackUnchanged(t *testing.T) {
	store := openHistory(t, dbPath(t))
	written := entry(p1, noon, "web")
	written.Message = "the api server said no"
	written.Outcome = "refused"
	record(t, store, written)

	page := recent(t, store, Query{})

	if len(page.Entries) != 1 {
		t.Fatalf("found %d entries, want 1", len(page.Entries))
	}
	got := page.Entries[0]
	got.ID = 0
	if got != written {
		t.Fatalf("entry = %+v, want %+v", got, written)
	}
}

func TestTheInstantSurvivesToTheMillisecond(t *testing.T) {
	store := openHistory(t, dbPath(t))
	at := time.Date(2026, 8, 29, 12, 0, 0, 123_000_000, time.UTC)
	record(t, store, entry(p1, at, "web"))

	got := recent(t, store, Query{}).Entries[0].At

	if !got.Equal(at) {
		t.Fatalf("at = %s, want %s; the stored unit must be milliseconds", got, at)
	}
}

func TestAnInstantInAnotherZoneComesBackAsUTC(t *testing.T) {
	store := openHistory(t, dbPath(t))
	zone := time.FixedZone("CEST", 2*60*60)
	at := time.Date(2026, 8, 29, 14, 0, 0, 0, zone)
	record(t, store, entry(p1, at, "web"))

	got := recent(t, store, Query{}).Entries[0].At

	if !got.Equal(at) {
		t.Fatalf("at = %s, want the same instant as %s", got, at)
	}
	if got.Location() != time.UTC {
		t.Fatalf("location = %s, want UTC so two windows agree on what a row says", got.Location())
	}
}

func TestEveryRowCarriesAnIdentityOfItsOwn(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "first"))
	record(t, store, entry(p1, noon, "second"))

	page := recent(t, store, Query{})

	if page.Entries[0].ID == page.Entries[1].ID {
		t.Fatal("two rows share an id; a list has no stable key to draw them by")
	}
	if page.Entries[0].ID == 0 {
		t.Fatal("a row came back without an id")
	}
}

func TestHistorySurvivesARestart(t *testing.T) {
	path := dbPath(t)
	store := openHistory(t, path)
	record(t, store, entry(p1, noon, "web"))
	if err := store.Close(); err != nil {
		t.Fatalf("close: %v", err)
	}

	page := recent(t, openHistory(t, path), Query{})

	if len(page.Entries) != 1 {
		t.Fatalf("found %d entries after reopening, want 1", len(page.Entries))
	}
}

func TestTheNewestIsFirst(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "older"))
	record(t, store, entry(p1, noon.Add(time.Minute), "newer"))

	page := recent(t, store, Query{})

	if got := names(page); got[0] != "newer" {
		t.Fatalf("order = %v, want the newest first", got)
	}
}

func TestTwoEntriesAtTheSameInstantKeepAStableOrder(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "first"))
	record(t, store, entry(p1, noon, "second"))

	page := recent(t, store, Query{})

	if got := names(page); got[0] != "second" || got[1] != "first" {
		t.Fatalf("order = %v, want the later write first so rows do not shuffle between polls", got)
	}
}

func TestOneClusterAtATime(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "here"))
	record(t, store, entry(p2, noon, "elsewhere"))

	page := recent(t, store, Query{Cluster: p1})

	if got := names(page); len(got) != 1 || got[0] != "here" {
		t.Fatalf("entries = %v, want only the one from the cluster asked for", got)
	}
}

func TestNoClusterMeansEveryCluster(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "here"))
	record(t, store, entry(p2, noon, "elsewhere"))

	if got := names(recent(t, store, Query{})); len(got) != 2 {
		t.Fatalf("entries = %v, want both clusters", got)
	}
}

func TestAPageThatLeftSomethingOutSaysSo(t *testing.T) {
	store := openHistory(t, dbPath(t))
	for at := range 4 {
		record(t, store, entry(p1, noon.Add(time.Duration(at)*time.Second), "one"))
	}

	page := recent(t, store, Query{Limit: 2})

	if len(page.Entries) != 2 {
		t.Fatalf("returned %d entries, want 2", len(page.Entries))
	}
	if !page.More {
		t.Fatal("the page dropped two entries and did not say so")
	}
}

func TestAPageThatHeldEverythingSaysThatToo(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "web"))

	page := recent(t, store, Query{Limit: 2})

	if page.More {
		t.Fatal("a complete page claimed there was more")
	}
}

func TestForgettingClearsEverything(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "web"))
	record(t, store, entry(p2, noon, "elsewhere"))

	if err := store.Forget(t.Context(), ""); err != nil {
		t.Fatalf("forget: %v", err)
	}

	if got := names(recent(t, store, Query{})); len(got) != 0 {
		t.Fatalf("entries = %v, want none after clearing", got)
	}
}

func TestForgettingRollsBackWhenOneHistoryTableCannotBeCleared(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "web"))
	err := store.Timeline(p1).Note(t.Context(), []Change{{At: noon, Name: "web"}})
	if err != nil {
		t.Fatalf("record change: %v", err)
	}
	_, triggerErr := store.writes.ExecContext(t.Context(), `
CREATE TRIGGER refuse_change_delete
BEFORE DELETE ON changes
BEGIN
	SELECT RAISE(ABORT, 'changes cannot be deleted');
END`)
	if triggerErr != nil {
		t.Fatalf("create trigger: %v", triggerErr)
	}

	forgetErr := store.Forget(t.Context(), p1)

	if forgetErr == nil {
		t.Fatal("forgetting succeeded despite the second table refusing its delete")
	}
	if got := names(recent(t, store, Query{Cluster: p1})); len(got) != 1 {
		t.Fatalf("audit entries = %v, want the first delete rolled back", got)
	}
	changes, changedErr := store.Changed(t.Context(), Query{Cluster: p1})
	if changedErr != nil {
		t.Fatalf("read changes: %v", changedErr)
	}
	if len(changes.Rows) != 1 {
		t.Fatalf("changes = %d, want the original row", len(changes.Rows))
	}
}

func TestTwoStoresWritingAtOnceBothLand(t *testing.T) {
	path := dbPath(t)
	one := openHistory(t, path)
	two := openHistory(t, path)
	const each = 25

	var wg sync.WaitGroup
	for _, store := range []*Store{one, two} {
		wg.Add(1)
		go func(into *Store) {
			defer wg.Done()
			for at := range each {
				held := entry(p1, noon.Add(time.Duration(at)*time.Second), "web")
				if err := into.For(p1).Record(context.Background(), held); err != nil {
					t.Errorf("record: %v", err)
					return
				}
			}
		}(store)
	}
	wg.Wait()

	page := recent(t, one, Query{Limit: maxLimit})
	if len(page.Entries) != each*2 {
		t.Fatalf("found %d entries, want %d; a second window's writes were lost", len(page.Entries), each*2)
	}
}

func TestAWindowSeesWhatTheOtherWrote(t *testing.T) {
	path := dbPath(t)
	one := openHistory(t, path)
	two := openHistory(t, path)

	record(t, one, entry(p1, noon, "written by the first"))

	if got := names(recent(t, two, Query{})); len(got) != 1 {
		t.Fatalf("entries = %v, want the other window's write to be visible", got)
	}
}

func TestMigratingTwiceChangesNothing(t *testing.T) {
	path := dbPath(t)
	store := openHistory(t, path)
	record(t, store, entry(p1, noon, "web"))
	if err := store.Close(); err != nil {
		t.Fatalf("close: %v", err)
	}

	again := openHistory(t, path)

	if len(recent(t, again, Query{}).Entries) != 1 {
		t.Fatal("reopening re-ran the schema and lost what was recorded")
	}
}

func TestAnOlderAuditRowIsKeptWithAnUnknownActor(t *testing.T) {
	path := dbPath(t)
	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		t.Fatalf("mkdir: %v", err)
	}
	db := sql.OpenDB(connector{dsn: path + pragmas})
	for version, statements := range migrations[:len(migrations)-1] {
		if err := apply(t.Context(), db, statements, version+1); err != nil {
			t.Fatalf("apply version %d: %v", version+1, err)
		}
	}
	_, err := db.ExecContext(
		t.Context(), `
INSERT INTO audit (
	cluster, at, verb, api_group, api_version, resource, kind,
	namespace, name, detail, outcome, message
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
		p1, noon.UnixMilli(), "delete", "apps", "v1", "deployments", "Deployment",
		"default", "web", "deleted the deployment", "done", "",
	)
	if err != nil {
		t.Fatalf("insert legacy audit row: %v", err)
	}
	if err := db.Close(); err != nil {
		t.Fatalf("close legacy database: %v", err)
	}

	got := recent(t, openHistory(t, path), Query{}).Entries

	if len(got) != 1 {
		t.Fatalf("entries = %d, want the legacy row", len(got))
	}
	if got[0].Actor != "unknown" {
		t.Fatalf("actor = %q, want unknown for a row written before actors were recorded", got[0].Actor)
	}
	if got[0].Name != "web" {
		t.Fatalf("name = %q, want the legacy row to survive", got[0].Name)
	}
}

func TestClosingTwiceIsFine(t *testing.T) {
	store := openHistory(t, dbPath(t))

	if err := store.Close(); err != nil {
		t.Fatalf("close: %v", err)
	}
	if err := store.Close(); err != nil {
		t.Fatalf("second close: %v", err)
	}
}

func TestADatabaseThatStopsAnsweringIsReported(t *testing.T) {
	store := openHistory(t, dbPath(t))
	store.mu.Lock()
	for _, db := range []*sql.DB{store.writes, store.reads} {
		if err := db.Close(); err != nil {
			t.Fatalf("close: %v", err)
		}
	}
	store.mu.Unlock()

	if err := store.For(p1).Record(t.Context(), entry(p1, noon, "web")); err == nil {
		t.Fatal("recording into a closed database reported success")
	}
	if _, err := store.Recent(t.Context(), Query{}); err == nil {
		t.Fatal("reading a closed database reported success")
	}
	if err := store.Forget(t.Context(), ""); err == nil {
		t.Fatal("clearing a closed database reported success")
	}
}

func TestAnEntryWithNoRoomForItIsReported(t *testing.T) {
	store := openHistory(t, dbPath(t))
	if _, err := store.writes.ExecContext(t.Context(), "DROP TABLE audit"); err != nil {
		t.Fatalf("drop: %v", err)
	}

	if recordErr := store.For(p1).Record(t.Context(), entry(p1, noon, "web")); recordErr == nil {
		t.Fatal("recording into a missing table reported success")
	}
	if _, readErr := store.Recent(t.Context(), Query{}); readErr == nil {
		t.Fatal("reading a missing table reported success")
	}
}

func TestAColumnThatChangedShapeIsReported(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "web"))
	if _, err := store.writes.ExecContext(t.Context(), "UPDATE audit SET at = 'not a number'"); err != nil {
		t.Fatalf("corrupt: %v", err)
	}

	_, err := store.Recent(t.Context(), Query{})

	if err == nil {
		t.Fatal("a row that could not be read came back as a silent zero")
	}
	if !strings.Contains(err.Error(), "store") {
		t.Fatalf("error = %q, want it to say what it was doing", err.Error())
	}
}

func TestTheAuditHalfIsBoundedByItsOwnCursor(t *testing.T) {
	store := openHistory(t, dbPath(t))
	for at := range 5 {
		record(t, store, entry(p1, noon.Add(time.Duration(at)*time.Minute), "row-"+strconv.Itoa(at)))
	}

	whole := recent(t, store, Query{})
	if len(whole.Entries) != 5 {
		t.Fatalf("read %d rows", len(whole.Entries))
	}
	boundary := whole.Entries[1].ID

	below := recent(t, store, Query{AfterAction: boundary})

	if len(below.Entries) != 3 {
		t.Fatalf("read %d rows below the cursor, want the three older ones", len(below.Entries))
	}
	for _, one := range below.Entries {
		if one.ID >= boundary {
			t.Fatalf("row %d came back at or above the cursor %d", one.ID, boundary)
		}
	}
}

func TestAuditPaginationFollowsTimestampOrder(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "oldest"))
	record(t, store, entry(p1, noon.Add(2*time.Minute), "newest"))
	record(t, store, entry(p1, noon.Add(time.Minute), "middle"))

	first := recent(t, store, Query{Limit: 2})
	second := recent(t, store, Query{Limit: 2, AfterAction: first.Entries[1].ID})

	if got := names(first); !slices.Equal(got, []string{"newest", "middle"}) {
		t.Fatalf("first page = %v", got)
	}
	if got := names(second); !slices.Equal(got, []string{"oldest"}) {
		t.Fatalf("second page = %v", got)
	}
}

func TestNoAuditCursorReadsFromTheTop(t *testing.T) {
	store := openHistory(t, dbPath(t))
	record(t, store, entry(p1, noon, "only"))

	if len(recent(t, store, Query{AfterAction: 0}).Entries) != 1 {
		t.Fatal("a zero cursor hid the newest row")
	}
}