package index
import (
"strings"
"testing"
"craftdesign.group/quarry/internal/artifact"
)
// Every ticket page asks for the tracker's field names (ticket.config)
// and its recorded numbers (ticket.get), and both are scans of every
// ticket_field row: about 280ms on tcl's 300k rows, per render, for
// answers that only move when a fold does. So they are kept per fold
// generation. The proof that the answer is memoized is a row written
// behind the fold's back: it is not seen until a fold lands, and then
// it is.
func TestTheTrackerCensusIsKeptUntilAFold(t *testing.T) {
ix := openIndex(t)
tkt := func(n byte, fields ...artifact.Field) Parsed {
return Parsed{Hash: strings.Repeat(string([]byte{'c', n}), 20),
Art: &artifact.Artifact{Type: artifact.Ticket, TicketID: strings.Repeat(string([]byte{'a', n}), 20),
Date: "2026-08-15T10:00:00", User: "dan", Fields: fields}}
}
if err := ix.FoldNow([]Parsed{tkt('1', artifact.Field{Name: "title", Value: "one"},
artifact.Field{Name: "number", Value: "7"})}); err != nil {
t.Fatal(err)
}
names := func() map[string]int {
uses, err := ix.TicketFieldNames()
if err != nil {
t.Fatal(err)
}
out := map[string]int{}
for _, u := range uses {
out[u.Name] = u.Tickets
}
return out
}
numbers := func() map[string]int {
n, err := ix.TicketRecordedNumbers()
if err != nil {
t.Fatal(err)
}
return n
}
if names()["title"] != 1 || numbers()[strings.Repeat("a1", 20)] != 7 {
t.Fatalf("FAIL: first read names=%v numbers=%v", names(), numbers())
}
// A caller that edits what it was handed must not edit the memo.
numbers()[strings.Repeat("a1", 20)] = 99
if numbers()[strings.Repeat("a1", 20)] != 7 {
t.Fatal("FAIL: a caller's edit reached the memoized numbers")
}
if _, err := ix.w.Exec(`INSERT INTO ticket_field (hash, name, value) VALUES (?, 'assignee', 'dan'), (?, 'number', '8')`,
strings.Repeat("c1", 20)+"x", strings.Repeat("c1", 20)+"y"); err != nil {
t.Fatal(err)
}
if _, ok := names()["assignee"]; ok {
t.Fatal("FAIL: the field names were scanned again without a fold")
}
if err := ix.FoldNow([]Parsed{tkt('2', artifact.Field{Name: "title", Value: "two"},
artifact.Field{Name: "number", Value: "9"})}); err != nil {
t.Fatal(err)
}
if names()["assignee"] != 1 || names()["title"] != 2 {
t.Fatalf("FAIL: a fold did not refresh the field names: %v", names())
}
if numbers()[strings.Repeat("a2", 20)] != 9 {
t.Fatalf("FAIL: a fold did not refresh the recorded numbers: %v", numbers())
}
t.Log("ok: field names and recorded numbers are scanned once per fold and copied out")
}
// A rebuild folds through FoldBulk and ends in FinishBulk, and Reset
// drops the tables outright: both must drop the census memo too, or a
// rebuilt tracker answers with the field names and numbers of the one
// it replaced.
func TestFinishBulkAndResetDropTheTrackerCensus(t *testing.T) {
ix := openIndex(t)
tkt := func(n byte, fields ...artifact.Field) Parsed {
return Parsed{Hash: strings.Repeat(string([]byte{'c', n}), 20),
Art: &artifact.Artifact{Type: artifact.Ticket, TicketID: strings.Repeat(string([]byte{'a', n}), 20),
Date: "2026-08-15T10:00:00", User: "dan", Fields: fields}}
}
if err := ix.FoldNow([]Parsed{tkt('1', artifact.Field{Name: "title", Value: "one"})}); err != nil {
t.Fatal(err)
}
names := func() map[string]int {
uses, err := ix.TicketFieldNames()
if err != nil {
t.Fatal(err)
}
out := map[string]int{}
for _, u := range uses {
out[u.Name] = u.Tickets
}
return out
}
numbers := func() map[string]int {
n, err := ix.TicketRecordedNumbers()
if err != nil {
t.Fatal(err)
}
return n
}
if names()["title"] != 1 || len(numbers()) != 0 {
t.Fatalf("FAIL: first read names=%v numbers=%v", names(), numbers())
}
if err := ix.FoldBulk([]Parsed{tkt('2', artifact.Field{Name: "assignee", Value: "lou"},
artifact.Field{Name: "number", Value: "4"})}); err != nil {
t.Fatal(err)
}
if err := ix.FinishBulk(); err != nil {
t.Fatal(err)
}
if names()["assignee"] != 1 || numbers()[strings.Repeat("a2", 20)] != 4 {
t.Fatalf("FAIL: FinishBulk kept the census from before the rebuild: names=%v numbers=%v", names(), numbers())
}
if err := ix.Reset(); err != nil {
t.Fatal(err)
}
if n, m := names(), numbers(); len(n) != 0 || len(m) != 0 {
t.Fatalf("FAIL: Reset kept the census of the dropped tables: names=%v numbers=%v", n, m)
}
t.Log("ok: FinishBulk and Reset each drop the field names and recorded numbers")
}