Ctrl K
ComunidadGuías…

internal/issues/page_test.go

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

Archivo original ↗Preguntar a KiBot
package issues

import (
	"encoding/base64"
	"errors"
	"slices"
	"strconv"
	"testing"
	"time"

	"github.com/sophotechlabs/spinoza/internal/api"
)

func rowsAcrossEveryOrderingField() []api.Issue {
	severities := []string{api.SeverityLow, api.SeverityMedium, api.SeverityHigh, "info"}
	folded := []int{0, 3, 50}
	when := []string{
		"",
		testNow.Add(-time.Hour).UTC().Format(time.RFC3339),
		testNow.UTC().Format(time.RFC3339),
	}
	clusters := []string{"alpha", "beta"}
	out := make([]api.Issue, 0, len(severities)*len(folded)*len(when)*len(clusters))
	for _, severity := range severities {
		for _, fold := range folded {
			for _, since := range when {
				for _, cluster := range clusters {
					out = append(out, api.Issue{
						ID:       cluster + "/" + severity + "/" + strconv.Itoa(fold) + "/" + since,
						Severity: severity,
						Folded:   fold,
						Since:    since,
						Cluster:  cluster,
					})
				}
			}
		}
	}
	return out
}

func TestTheCursorKeyOrdersRowsTheWayRankDoes(t *testing.T) {
	rows := rowsAcrossEveryOrderingField()
	Rank(rows, ByWorst)

	for at := 1; at < len(rows); at++ {
		before := issueKey(rows[at-1], ByWorst)
		after := issueKey(rows[at], ByWorst)
		if before >= after {
			t.Fatalf(
				"row %d sorts before row %d but its key does not: %q >= %q",
				at-1, at, before, after,
			)
		}
	}
}

func TestPagingWalksTheWholeQueueInOrder(t *testing.T) {
	rows := rowsAcrossEveryOrderingField()
	Rank(rows, ByWorst)

	walked := []api.Issue{}
	cursor := ""
	for range rows {
		page, next := Page(rows, cursorKey(t, cursor, ByWorst), 7, ByWorst)
		walked = append(walked, page...)
		if next == "" {
			break
		}
		cursor = next
	}

	if len(walked) != len(rows) {
		t.Fatalf("walked %d rows, queue holds %d", len(walked), len(rows))
	}
	for at := range rows {
		if walked[at].ID != rows[at].ID {
			t.Fatalf("row %d is %q, ranked order has %q", at, walked[at].ID, rows[at].ID)
		}
	}
}

func TestEveryPageButTheLastIsFull(t *testing.T) {
	rows := rowsAcrossEveryOrderingField()
	Rank(rows, ByWorst)

	cursor := ""
	pages := 0
	for {
		page, next := Page(rows, cursorKey(t, cursor, ByWorst), 7, ByWorst)
		pages++
		if next == "" {
			if len(page) > 7 {
				t.Fatalf("last page carries %d rows, the limit is 7", len(page))
			}
			break
		}
		if len(page) != 7 {
			t.Fatalf("page %d carries %d rows, the limit is 7", pages, len(page))
		}
		cursor = next
	}
	if pages != (len(rows)+6)/7 {
		t.Fatalf("walked %d pages, %d rows at 7 a page needs %d", pages, len(rows), (len(rows)+6)/7)
	}
}

func TestAPageBigEnoughForTheQueueEndsIt(t *testing.T) {
	rows := rowsAcrossEveryOrderingField()
	Rank(rows, ByWorst)

	page, next := Page(rows, "", len(rows)+1, ByWorst)

	if len(page) != len(rows) {
		t.Fatalf("page carries %d of %d rows", len(page), len(rows))
	}
	if next != "" {
		t.Fatalf("a page holding the whole queue still offers %q", next)
	}
}

func TestAnEmptyQueuePagesToNothing(t *testing.T) {
	page, next := Page(nil, "", Shown, ByWorst)

	if len(page) != 0 {
		t.Fatalf("an empty queue paged to %d rows", len(page))
	}
	if next != "" {
		t.Fatalf("an empty queue offers %q", next)
	}
}

func TestAnInvalidPageLimitStillMakesProgress(t *testing.T) {
	rows := rowsAcrossEveryOrderingField()
	Rank(rows, ByWorst)
	for _, limit := range []int{0, -1, -100} {
		page, next := Page(rows, "", limit, ByWorst)
		if len(page) == 0 {
			t.Fatalf("limit %d returned an empty page for a non-empty queue", limit)
		}
		if len(page) > Shown {
			t.Fatalf("limit %d returned %d rows, want at most %d", limit, len(page), Shown)
		}
		if len(rows) > Shown && next == "" {
			t.Fatalf("limit %d lost the continuation", limit)
		}
	}
}

func TestARowClearingBeforeTheNextPageDoesNotSkipItsNeighbour(t *testing.T) {
	rows := rowsAcrossEveryOrderingField()
	Rank(rows, ByWorst)
	first, next := Page(rows, "", 7, ByWorst)
	if next == "" {
		t.Fatal("the fixture is too small to have a second page")
	}

	shorter := slices.Delete(slices.Clone(rows), 0, 1)
	second, _ := Page(shorter, cursorKey(t, next, ByWorst), 7, ByWorst)

	if len(second) == 0 {
		t.Fatal("the second page came back empty")
	}
	if second[0].ID != rows[len(first)].ID {
		t.Fatalf(
			"the row after the boundary is %q, a row clearing above it made it %q",
			rows[len(first)].ID, second[0].ID,
		)
	}
}

func TestGarbageInTheCursorIsRefused(t *testing.T) {
	_, err := DecodeCursor("not base64 at all!!", ByWorst)

	if !errors.Is(err, ErrInvalidCursor) {
		t.Fatalf("error = %v, want invalid cursor", err)
	}
}

func TestIncompleteCursorsAreRefused(t *testing.T) {
	for _, raw := range []string{ByWorst, "\x00key", ByWorst + "\x00"} {
		cursor := base64.RawURLEncoding.EncodeToString([]byte(raw))
		_, err := DecodeCursor(cursor, ByWorst)
		if !errors.Is(err, ErrInvalidCursor) {
			t.Fatalf("cursor for %q returned %v, want invalid cursor", raw, err)
		}
	}
}

func cursorKey(t *testing.T, cursor, order string) string {
	t.Helper()
	key, err := DecodeCursor(cursor, order)
	if err != nil {
		t.Fatalf("decode cursor: %v", err)
	}
	return key
}

func TestAnEmptyCursorEncodesToNothing(t *testing.T) {
	if EncodeCursor("", ByWorst) != "" {
		t.Fatalf("an empty key encoded to %q", EncodeCursor("", ByWorst))
	}
	if cursorKey(t, "", ByWorst) != "" {
		t.Fatal("an empty cursor decoded to something")
	}
}

func TestACursorFromAnotherOrderIsRefused(t *testing.T) {
	rows := rowsAcrossEveryOrderingField()
	Rank(rows, ByWorst)
	_, next := Page(rows, "", 3, ByWorst)
	if next == "" {
		t.Fatal("the fixture produced no second page")
	}

	_, err := DecodeCursor(next, ByNewest)

	if !errors.Is(err, ErrCursorOrder) {
		t.Fatalf("error = %v, want cursor order mismatch", err)
	}
}

func TestPageSizeFallsBackToWhatOnePageShows(t *testing.T) {
	if PageSize(0) != Shown {
		t.Fatalf("an unset size gave %d, one page shows %d", PageSize(0), Shown)
	}
	if PageSize(-4) != Shown {
		t.Fatalf("a negative size gave %d, one page shows %d", PageSize(-4), Shown)
	}
	if PageSize(9) != 9 {
		t.Fatalf("a size of 9 gave %d", PageSize(9))
	}
}

func TestCountDownStaysInsideItsWidth(t *testing.T) {
	cases := []struct {
		value int
		want  string
	}{
		{value: 0, want: "999"},
		{value: 1, want: "998"},
		{value: 999, want: "000"},
		{value: -7, want: "999"},
		{value: 4000, want: "000"},
	}
	for _, one := range cases {
		got := countDown(one.value, 999)
		if got != one.want {
			t.Fatalf("countDown(%d, 999) = %q, want %q", one.value, got, one.want)
		}
	}
}

func TestCountUpStaysInsideItsWidth(t *testing.T) {
	cases := []struct {
		value int
		want  string
	}{
		{value: 0, want: "000"},
		{value: 1, want: "001"},
		{value: 999, want: "999"},
		{value: -7, want: "000"},
		{value: 4000, want: "999"},
	}
	for _, one := range cases {
		got := countUp(one.value, 999)
		if got != one.want {
			t.Fatalf("countUp(%d, 999) = %q, want %q", one.value, got, one.want)
		}
	}
}

func TestATimeNobodyStampedCountsAsTheOldest(t *testing.T) {
	if secondsOf(time.Time{}) != 0 {
		t.Fatalf("an unstamped time counted as %d seconds", secondsOf(time.Time{}))
	}
	stamped := time.Unix(1700000000, 0)
	if secondsOf(stamped) != 1700000000 {
		t.Fatalf("a stamped time counted as %d seconds", secondsOf(stamped))
	}
}

func TestAFoldCountPastTheCeilingStillOutranksASmallOne(t *testing.T) {
	huge := api.Issue{ID: "huge", Severity: api.SeverityLow, Folded: foldCeiling + 10}
	modest := api.Issue{ID: "modest", Severity: api.SeverityLow, Folded: 1}

	if issueKey(huge, ByWorst) >= issueKey(modest, ByWorst) {
		t.Fatalf("a fold count past the ceiling wrapped and sorted below a fold of 1")
	}
}

func TestNewestFirstOrdersByWhenNotBySeverity(t *testing.T) {
	rows := []api.Issue{
		{ID: "old-fatal", Severity: api.SeverityHigh, Since: "2026-08-01T00:00:00Z"},
		{ID: "new-warning", Severity: api.SeverityLow, Since: "2026-08-29T00:00:00Z"},
	}

	Rank(rows, ByNewest)

	if rows[0].ID != "new-warning" {
		t.Fatalf("newest first put %q on top", rows[0].ID)
	}
}

func TestOldestFirstIsTheOtherWayRound(t *testing.T) {
	rows := []api.Issue{
		{ID: "new-warning", Severity: api.SeverityLow, Since: "2026-08-29T00:00:00Z"},
		{ID: "old-fatal", Severity: api.SeverityHigh, Since: "2026-08-01T00:00:00Z"},
	}

	Rank(rows, ByOldest)

	if rows[0].ID != "old-fatal" {
		t.Fatalf("oldest first put %q on top", rows[0].ID)
	}
}

func TestEveryOrderWalksItsWholeQueueWithoutRepeating(t *testing.T) {
	for _, order := range []string{ByWorst, ByNewest, ByOldest} {
		t.Run(order, func(t *testing.T) {
			rows := rowsAcrossEveryOrderingField()
			Rank(rows, order)

			for at := 1; at < len(rows); at++ {
				if issueKey(rows[at-1], order) >= issueKey(rows[at], order) {
					t.Fatalf("row %d sorts before row %d but its key does not", at-1, at)
				}
			}

			seen := map[string]bool{}
			cursor := ""
			for range rows {
				page, next := Page(rows, cursorKey(t, cursor, order), 7, order)
				for _, row := range page {
					if seen[row.ID] {
						t.Fatalf("%s came back twice", row.ID)
					}
					seen[row.ID] = true
				}
				if next == "" {
					break
				}
				cursor = next
			}
			if len(seen) != len(rows) {
				t.Fatalf("walked %d of %d rows", len(seen), len(rows))
			}
		})
	}
}

func TestAnUnknownOrderFallsBackToWorstFirst(t *testing.T) {
	for _, asked := range []string{"", "sideways", "WORST"} {
		if OrderOf(asked) != ByWorst {
			t.Fatalf("%q read as %q", asked, OrderOf(asked))
		}
	}
	if OrderOf(ByNewest) != ByNewest || OrderOf(ByOldest) != ByOldest {
		t.Fatal("a date order was not taken")
	}
}