package index
import (
"testing"
"time"
"craftdesign.group/quarry/internal/artifact"
)
// EventKinds is a full scan of the event table — covering index or not,
// that is every artifact in the store — and the dashboard asks EVERY
// repository for it on the landing page. Measured cold across this
// fleet it was 610ms of one page's time, for numbers that only move
// when a fold does.
func TestEventKindsIsMemoisedAndRefreshes(t *testing.T) {
ix := openIndex(t)
if got, err := ix.EventKinds(); err != nil || len(got) != 0 {
t.Fatalf("empty index: %v err=%v", got, err)
}
if err := ix.FoldNow([]Parsed{
{Hash: "c1", Art: &artifact.Artifact{Type: artifact.Manifest,
Date: "2026-01-01T00:00:00", User: "dan", Comment: "one"}},
}); err != nil {
t.Fatal(err)
}
got, err := ix.EventKinds()
if err != nil {
t.Fatal(err)
}
if got["ci"] != 1 {
t.Fatalf("after fold: %v, want one check-in — the memo went stale", got)
}
// It IS memoised: a write behind the fold's back is not seen...
if _, err := ix.db.Exec(`DELETE FROM event`); err != nil {
t.Fatal(err)
}
if got, _ := ix.EventKinds(); got["ci"] != 1 {
t.Error("not memoised; every dashboard render pays the scan")
}
// ...and a fold refreshes it, which is the only thing that moves
// these numbers in practice. The refresh runs in the background, so
// the census catches up rather than being current on return.
if err := ix.FoldNow([]Parsed{
{Hash: "c2", Art: &artifact.Artifact{Type: artifact.Manifest,
Date: "2026-01-02T00:00:00", User: "dan", Comment: "two"}},
}); err != nil {
t.Fatal(err)
}
// Compared against the table itself rather than a number I guessed:
// the property is that the memo matches reality after a fold, not
// that reality holds any particular count.
var live int
if err := ix.db.QueryRow(`SELECT count(*) FROM event WHERE kind = 'ci'`).Scan(&live); err != nil {
t.Fatal(err)
}
deadline := time.Now().Add(2 * time.Second)
got, _ = ix.EventKinds()
for got["ci"] != live && time.Now().Before(deadline) {
time.Sleep(5 * time.Millisecond)
got, _ = ix.EventKinds()
}
if got["ci"] != live {
t.Errorf("after refold the memo says %d and the table holds %d", got["ci"], live)
}
}
// The memo outlives the call, so handing a caller the map itself makes
// every reader a potential writer of everyone else's counts.
func TestEventKindsHandsBackACopy(t *testing.T) {
ix := openIndex(t)
if err := ix.FoldNow([]Parsed{
{Hash: "c1", Art: &artifact.Artifact{Type: artifact.Manifest,
Date: "2026-01-01T00:00:00", User: "dan", Comment: "one"}},
}); err != nil {
t.Fatal(err)
}
first, _ := ix.EventKinds()
first["ci"] = 9999
delete(first, "ci")
second, _ := ix.EventKinds()
if second["ci"] != 1 {
t.Fatalf("a caller's edit reached the memo: %v", second)
}
}