package index
import (
"strings"
"testing"
"craftdesign.group/quarry/internal/artifact"
)
// tieChain folds a root and a chain of three check-ins committed in the
// same second, whose hashes fall as the chain grows: ordered by hash,
// each parent would sort above its child.
func tieChain(t *testing.T) *Index {
t.Helper()
ix := openIndex(t)
h := func(b string) string { return strings.Repeat(b, 32) }
ci := func(hash, date, parent string) Parsed {
art := &artifact.Artifact{Type: artifact.Manifest, User: "dan", Date: date, Comment: hash[:2]}
if parent != "" {
art.Parents = []string{parent}
}
return Parsed{Hash: hash, Art: art}
}
batch := []Parsed{
ci(h("11"), "2026-10-25T22:00:00", ""),
ci(h("ee"), "2026-10-25T22:48:06", h("11")),
ci(h("cc"), "2026-10-25T22:48:06", h("ee")),
ci(h("aa"), "2026-10-25T22:48:06", h("cc")),
}
if err := ix.FoldNow(batch); err != nil {
t.Fatal(err)
}
return ix
}
func tieOrder(es []TimelineEntry) string {
var b []string
for _, e := range es {
b = append(b, e.Hash[:2])
}
return strings.Join(b, " ")
}
// Check-ins in the same second are shown child above parent, as the
// graph draws them; their hashes do not decide it.
func TestTimelineShowsASameSecondChildAboveItsParent(t *testing.T) {
ix := tieChain(t)
es, _, err := ix.Timeline(TimelineFilter{Kind: "ci", Limit: 10})
if err != nil {
t.Fatal(err)
}
if got := tieOrder(es); got != "aa cc ee 11" {
t.Fatalf("FAIL: order %q; want aa cc ee 11", got)
}
t.Log("ok: a same-second chain reads child first")
}
// Paging through a same-second run still shows every check-in once: the
// rows a cursor is taken from stay where the query put them.
func TestTimelinePagesThroughASameSecondRunOnce(t *testing.T) {
ix := tieChain(t)
seen := map[string]int{}
f := TimelineFilter{Kind: "ci", Limit: 2}
for range 5 {
es, more, err := ix.Timeline(f)
if err != nil {
t.Fatal(err)
}
for _, e := range es {
seen[e.Hash[:2]]++
}
if !more {
break
}
last := es[len(es)-1]
f.Before = last.Date + "~" + last.Hash
}
for _, h := range []string{"11", "ee", "cc", "aa"} {
if seen[h] != 1 {
t.Fatalf("FAIL: pages showed %v; want each check-in once", seen)
}
}
t.Log("ok: paging shows each check-in once")
}
// A time-warped check-in, dated before its own parent, is shown above it.
// Git keeps a commit's author date through a rebase, so a rebased commit
// imported with --use-author sits on a trunk check-in newer than itself
// (Saur0n, 2026-11-01: a fix dated 18:07 on a 19:33 merge). By date it
// sorted below its parent and its line ran off the page.
func TestTimelineShowsATimeWarpedChildAboveItsParent(t *testing.T) {
ix := openIndex(t)
h := func(b string) string { return strings.Repeat(b, 32) }
ci := func(hash, date string, parents ...string) Parsed {
return Parsed{Hash: hash, Art: &artifact.Artifact{Type: artifact.Manifest, User: "lou", Date: date, Comment: hash[:2], Parents: parents}}
}
batch := []Parsed{
ci(h("11"), "2026-11-01T16:26:35"),
ci(h("22"), "2026-11-01T17:30:35", h("11")), // a fix on release-209
ci(h("33"), "2026-11-01T19:33:35", h("11"), h("22")), // land release-209
ci(h("44"), "2026-11-01T18:07:58", h("33")), // the rebased fix: before its parent
ci(h("55"), "2026-11-01T19:48:35", h("33"), h("44")), // land release-212
ci(h("66"), "2026-11-01T18:30:00", h("11")), // unrelated, between them by date
}
if err := ix.FoldNow(batch); err != nil {
t.Fatal(err)
}
es, _, err := ix.Timeline(TimelineFilter{Kind: "ci", Limit: 10})
if err != nil {
t.Fatal(err)
}
if got := tieOrder(es); got != "55 44 33 66 22 11" {
t.Fatalf("FAIL: order %q; want 55 44 33 66 22 11, the warped 44 just above its parent 33", got)
}
t.Log("ok: a time-warped check-in reads above its parent, the rest by date")
}