Ctrl K
ComunidadGuías…

internal/store/timeline_test.go

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

Archivo original ↗Preguntar a KiBot
package store

import (
	"slices"
	"strings"
	"testing"
	"time"
)

func change(at time.Time, name string) Change {
	return Change{
		At:        at,
		Verb:      Changed,
		Group:     "apps",
		Version:   "v1",
		Resource:  "deployments",
		Kind:      "Deployment",
		Namespace: "default",
		Name:      name,
		UID:       "uid-" + name,
		Cells:     []string{"1/1", "Running"},
	}
}

func noteOne(t *testing.T, store *Store, cluster string, one Change) {
	t.Helper()
	err := store.Timeline(cluster).Note(t.Context(), []Change{one})
	if err != nil {
		t.Fatalf("note: %v", err)
	}
}

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

	noteOne(t, store, p1, change(noon, "web"))

	found, err := store.Changed(t.Context(), Query{})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}
	if len(found.Rows) != 1 {
		t.Fatalf("wanted one change, got %d", len(found.Rows))
	}
	row := found.Rows[0]
	if row.Name != "web" || row.Kind != "Deployment" || row.UID != "uid-web" {
		t.Fatalf("the change came back as %+v", row)
	}
	if !row.At.Equal(noon) {
		t.Fatalf("wanted %s, got %s", noon, row.At)
	}
	if len(row.Cells) != 2 || row.Cells[1] != "Running" {
		t.Fatalf("the cells came back as %v", row.Cells)
	}
}

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

	noteOne(t, store, p1, change(noon, "web"))
	noteOne(t, store, p2, change(noon, "api"))

	found, err := store.Changed(t.Context(), Query{Cluster: p2})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}
	if len(found.Rows) != 1 {
		t.Fatalf("wanted one change, got %d", len(found.Rows))
	}
	if found.Rows[0].Name != "api" || found.Rows[0].Cluster != p2 {
		t.Fatalf("the change came back as %+v", found.Rows[0])
	}
}

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

	noteOne(t, store, p1, change(noon.Add(-time.Hour), "old"))
	noteOne(t, store, p1, change(noon, "new"))

	found, err := store.Changed(t.Context(), Query{})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}
	if found.Rows[0].Name != "new" || found.Rows[1].Name != "old" {
		t.Fatalf("the order came back as %s then %s", found.Rows[0].Name, found.Rows[1].Name)
	}
}

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

	err := store.Timeline(p1).Note(t.Context(), []Change{
		change(noon, "one"),
		change(noon, "two"),
		change(noon, "three"),
	})
	if err != nil {
		t.Fatalf("note: %v", err)
	}

	found, readErr := store.Changed(t.Context(), Query{})
	if readErr != nil {
		t.Fatalf("changed: %v", readErr)
	}
	if len(found.Rows) != 3 {
		t.Fatalf("wanted three changes, got %d", len(found.Rows))
	}
}

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

	err := store.Timeline(p1).Note(t.Context(), nil)
	if err != nil {
		t.Fatalf("note: %v", err)
	}

	found, readErr := store.Changed(t.Context(), Query{})
	if readErr != nil {
		t.Fatalf("changed: %v", readErr)
	}
	if len(found.Rows) != 0 {
		t.Fatalf("wanted no changes, got %d", len(found.Rows))
	}
}

func TestTheChangeQuerySaysWhenThereAreMore(t *testing.T) {
	store := openHistory(t, dbPath(t))
	noteOne(t, store, p1, change(noon, "one"))
	noteOne(t, store, p1, change(noon, "two"))

	found, err := store.Changed(t.Context(), Query{Limit: 1})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}
	if len(found.Rows) != 1 {
		t.Fatalf("wanted one change, got %d", len(found.Rows))
	}
	if !found.More {
		t.Fatal("wanted to be told there are more")
	}
}

func TestChangesOlderThanTheWindowArePruned(t *testing.T) {
	store := openHistory(t, dbPath(t))
	noteOne(t, store, p1, change(noon.AddDate(0, 0, -30), "ancient"))
	noteOne(t, store, p1, change(noon, "today"))

	err := store.Prune(t.Context(), Retention{Days: 7}, noon)
	if err != nil {
		t.Fatalf("prune: %v", err)
	}

	found, readErr := store.Changed(t.Context(), Query{})
	if readErr != nil {
		t.Fatalf("changed: %v", readErr)
	}
	if len(found.Rows) != 1 || found.Rows[0].Name != "today" {
		t.Fatalf("what survived the prune was %+v", found.Rows)
	}
}

func TestTheRowCapIsWhatHoldsOnAClusterNobodyMeasured(t *testing.T) {
	store := openHistory(t, dbPath(t))
	for at := range 10 {
		noteOne(t, store, p1, change(noon, "web-"+string(rune('a'+at))))
	}

	err := store.Prune(t.Context(), Retention{Rows: 4}, noon)
	if err != nil {
		t.Fatalf("prune: %v", err)
	}

	found, readErr := store.Changed(t.Context(), Query{})
	if readErr != nil {
		t.Fatalf("changed: %v", readErr)
	}
	if len(found.Rows) != 4 {
		t.Fatalf("wanted four changes left, got %d", len(found.Rows))
	}
	if found.Rows[0].Name != "web-j" {
		t.Fatalf("the newest kept was %s", found.Rows[0].Name)
	}
}

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

	err := store.Prune(t.Context(), Retention{Days: 7, Rows: 10}, noon)
	if err != nil {
		t.Fatalf("prune: %v", err)
	}
}

func TestPruningWithNoRetentionAskedForKeepsEverything(t *testing.T) {
	store := openHistory(t, dbPath(t))
	noteOne(t, store, p1, change(noon.AddDate(0, 0, -400), "ancient"))

	err := store.Prune(t.Context(), Retention{}, noon)
	if err != nil {
		t.Fatalf("prune: %v", err)
	}

	found, readErr := store.Changed(t.Context(), Query{})
	if readErr != nil {
		t.Fatalf("changed: %v", readErr)
	}
	if len(found.Rows) != 1 {
		t.Fatalf("wanted the row kept, got %d", len(found.Rows))
	}
}

func TestClearingAClustersHistoryTakesItsChangesToo(t *testing.T) {
	store := openHistory(t, dbPath(t))
	noteOne(t, store, p1, change(noon, "web"))
	noteOne(t, store, p2, change(noon, "api"))

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

	found, readErr := store.Changed(t.Context(), Query{})
	if readErr != nil {
		t.Fatalf("changed: %v", readErr)
	}
	if len(found.Rows) != 1 || found.Rows[0].Cluster != p2 {
		t.Fatalf("what survived was %+v", found.Rows)
	}
}

func TestAStoreThatCouldNotOpenSaysNothingRatherThanFailing(t *testing.T) {
	store := unavailable("nowhere to keep history")

	err := store.Timeline(p1).Note(t.Context(), []Change{change(noon, "web")})
	if err != nil {
		t.Fatalf("note: %v", err)
	}
	found, readErr := store.Changed(t.Context(), Query{})
	if readErr != nil {
		t.Fatalf("changed: %v", readErr)
	}
	if len(found.Rows) != 0 {
		t.Fatalf("wanted nothing back, got %d", len(found.Rows))
	}
	pruneErr := store.Prune(t.Context(), Retention{Days: 1, Rows: 1}, noon)
	if pruneErr != nil {
		t.Fatalf("prune: %v", pruneErr)
	}
}

func TestCellsThatCannotBeReadComeBackEmpty(t *testing.T) {
	store := openHistory(t, dbPath(t))
	noteOne(t, store, p1, Change{At: noon, Verb: Added, Name: "web", Cells: nil})

	found, err := store.Changed(t.Context(), Query{})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}
	if len(found.Rows[0].Cells) != 0 {
		t.Fatalf("wanted no cells, got %v", found.Rows[0].Cells)
	}
}

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

	noteErr := store.Timeline(p1).Note(t.Context(), []Change{change(noon, "web")})
	if noteErr == nil {
		t.Fatal("noting into a missing table reported success")
	}
	if _, readErr := store.Changed(t.Context(), Query{}); readErr == nil {
		t.Fatal("reading a missing table reported success")
	}
	if pruneErr := store.Prune(t.Context(), Retention{Days: 1}, noon); pruneErr == nil {
		t.Fatal("trimming a missing table reported success")
	}
}

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

	err := store.Prune(t.Context(), Retention{Rows: 10}, noon)

	if err == nil {
		t.Fatal("trimming a missing table reported success")
	}
}

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

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

	if err == nil {
		t.Fatal("a change 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 TestNotingIntoAClosedStoreIsReported(t *testing.T) {
	store := openHistory(t, dbPath(t))
	if err := store.writes.Close(); err != nil {
		t.Fatalf("close: %v", err)
	}

	noteErr := store.Timeline(p1).Note(t.Context(), []Change{change(noon, "web")})

	if noteErr == nil {
		t.Fatal("noting into a closed database reported success")
	}
}

func TestCellsThatAreNotAListComeBackEmpty(t *testing.T) {
	store := openHistory(t, dbPath(t))
	noteOne(t, store, p1, change(noon, "web"))
	if _, err := store.writes.ExecContext(t.Context(), "UPDATE changes SET cells = 'not json'"); err != nil {
		t.Fatalf("corrupt: %v", err)
	}

	found, err := store.Changed(t.Context(), Query{})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}
	if len(found.Rows[0].Cells) != 0 {
		t.Fatalf("wanted no cells, got %v", found.Rows[0].Cells)
	}
}

func TestTheLimitAStoreWillHonourIsSayable(t *testing.T) {
	if Limit(0) != defaultLimit {
		t.Fatalf("Limit(0) = %d", Limit(0))
	}
	if Limit(maxLimit+1) != maxLimit {
		t.Fatalf("Limit above the cap = %d", Limit(maxLimit+1))
	}
	if Limit(5) != 5 {
		t.Fatalf("Limit(5) = %d", Limit(5))
	}
}

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

	err := store.Tabs().Recolor(t.Context(), p1, 3)
	if err != nil {
		t.Fatalf("recolor: %v", err)
	}
	closed := unavailable("nowhere to keep history")
	if quietErr := closed.Tabs().Recolor(t.Context(), p1, 3); quietErr != nil {
		t.Fatalf("recolor: %v", quietErr)
	}
}

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

	err := store.Tabs().Recording(t.Context(), p1, "workloads")

	if err == nil {
		t.Fatal("recording against a missing table reported success")
	}
}

func TestAChangeRemembersWhatItMovedFrom(t *testing.T) {
	store := openHistory(t, dbPath(t))
	one := change(noon, "web")
	one.Was = []string{"2/2", "Running"}

	noteOne(t, store, p1, one)

	found, err := store.Changed(t.Context(), Query{})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}
	if len(found.Rows[0].Was) != 2 || found.Rows[0].Was[1] != "Running" {
		t.Fatalf("was = %v", found.Rows[0].Was)
	}
}

func TestAPageOfChangesContinuesBelowTheCursor(t *testing.T) {
	store := openHistory(t, dbPath(t))
	noteOne(t, store, p1, change(noon.Add(-2*time.Minute), "oldest"))
	noteOne(t, store, p1, change(noon.Add(-time.Minute), "middle"))
	noteOne(t, store, p1, change(noon, "newest"))
	first, err := store.Changed(t.Context(), Query{Limit: 1})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}

	next, nextErr := store.Changed(t.Context(), Query{Limit: 1, After: first.Rows[0].ID})

	if nextErr != nil {
		t.Fatalf("changed: %v", nextErr)
	}
	if next.Rows[0].Name != "middle" {
		t.Fatalf("the next page started at %s", next.Rows[0].Name)
	}
}

func TestChangePaginationFollowsTimestampOrder(t *testing.T) {
	store := openHistory(t, dbPath(t))
	noteOne(t, store, p1, change(noon, "oldest"))
	noteOne(t, store, p1, change(noon.Add(2*time.Minute), "newest"))
	noteOne(t, store, p1, change(noon.Add(time.Minute), "middle"))
	first, err := store.Changed(t.Context(), Query{Limit: 2})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}
	second, nextErr := store.Changed(t.Context(), Query{Limit: 2, After: first.Rows[1].ID})
	if nextErr != nil {
		t.Fatalf("changed: %v", nextErr)
	}

	firstNames := []string{first.Rows[0].Name, first.Rows[1].Name}
	if !slices.Equal(firstNames, []string{"newest", "middle"}) {
		t.Fatalf("first page = %v", firstNames)
	}
	secondNames := make([]string, 0, len(second.Rows))
	for _, row := range second.Rows {
		secondNames = append(secondNames, row.Name)
	}
	if !slices.Equal(secondNames, []string{"oldest"}) {
		t.Fatalf("second page = %v", secondNames)
	}
}

func TestTheLastPageSaysThereIsNoMore(t *testing.T) {
	store := openHistory(t, dbPath(t))
	noteOne(t, store, p1, change(noon, "only"))

	found, err := store.Changed(t.Context(), Query{Limit: 10})
	if err != nil {
		t.Fatalf("changed: %v", err)
	}
	if found.More {
		t.Fatal("a page that held everything said there was more")
	}
}