package index
import (
"strings"
"testing"
"craftdesign.group/quarry/internal/artifact"
)
// A TAG ADDED TO A CHECK-IN IS ONE ROW, ON THE CHECK-IN THAT CARRIES IT
// (RFC 0020 §releases, the `tag` event).
//
// What counts is what a person means by tagging: `fossil tag add NAME
// CHECKIN` writes `+sym-NAME`, `fossil commit --tag NAME` writes the
// same card inline, and `*sym-NAME` without a branch is a propagating
// tag on its carrier only. A branch's own `sym-` names, raw tags
// (closed, hidden, comment, ci-pass), cancellations, a tag since
// cancelled, and a tag on something that is not a check-in do not.
func TestTagsAddedListsTagsAddedToCheckins(t *testing.T) {
ix := openIndex(t)
a, b, c := strings.Repeat("a1", 32), strings.Repeat("b2", 32), strings.Repeat("c3", 32)
x, y, z := strings.Repeat("d4", 32), strings.Repeat("e5", 32), strings.Repeat("f6", 32)
manifest := func(hash, date string, parents []string, tags ...artifact.Tag) Parsed {
return Parsed{Hash: hash, Art: &artifact.Artifact{
Type: artifact.Manifest, Date: date, User: "dan",
Comment: "c", Parents: parents, Tags: tags}}
}
control := func(hash, date string, tags ...artifact.Tag) Parsed {
return Parsed{Hash: hash, Art: &artifact.Artifact{
Type: artifact.Control, Date: date, User: "tagger", Tags: tags}}
}
if err := ix.FoldNow([]Parsed{
manifest(a, "2026-09-01T10:00:00", nil,
artifact.Tag{Name: "*branch", Target: "*", Value: "trunk"},
artifact.Tag{Name: "*sym-trunk", Target: "*"}),
manifest(b, "2026-09-02T10:00:00", []string{a},
artifact.Tag{Name: "*branch", Target: "*", Value: "feature"},
artifact.Tag{Name: "*sym-feature", Target: "*"},
artifact.Tag{Name: "-sym-trunk", Target: "*"}),
manifest(c, "2026-09-03T10:00:00", []string{a},
artifact.Tag{Name: "+sym-version-0.1.0", Target: "*"}),
control(x, "2026-09-04T10:00:00",
artifact.Tag{Name: "+sym-version-0.2.0", Target: a},
artifact.Tag{Name: "*sym-rel", Target: a},
artifact.Tag{Name: "+sym-gone", Target: a},
artifact.Tag{Name: "+closed", Target: b},
artifact.Tag{Name: "+sym-elsewhere", Target: strings.Repeat("99", 32)}),
control(y, "2026-09-05T10:00:00",
artifact.Tag{Name: "-sym-gone", Target: a},
artifact.Tag{Name: "+ci-pass", Target: c, Value: "ci/test.yml"}),
control(z, "2026-09-06T10:00:00",
artifact.Tag{Name: "+sym-feature", Target: c}),
}); err != nil {
t.Fatal(err)
}
got, err := ix.TagsAdded()
if err != nil {
t.Fatal(err)
}
type row struct{ checkin, name, branch, src, tagger string }
want := []row{
{c, "version-0.1.0", "trunk", c, "dan"},
{a, "rel", "trunk", x, "tagger"},
{a, "version-0.2.0", "trunk", x, "tagger"},
}
if len(got) != len(want) {
t.Fatalf("FAIL: TagsAdded = %+v; want %d rows", got, len(want))
}
for i, w := range want {
g := got[i]
if g.Checkin != w.checkin || g.Name != w.name || g.Branch != w.branch || g.Src != w.src || g.Tagger != w.tagger {
t.Errorf("FAIL: row %d = %.4s %s on %s from %.4s by %q; want %.4s %s on %s from %.4s by %q",
i, g.Checkin, g.Name, g.Branch, g.Src, g.Tagger, w.checkin, w.name, w.branch, w.src, w.tagger)
}
}
// A tag that arrives before its check-in is listed once the
// check-in is folded, under the same key: the card on 99.. above.
late := strings.Repeat("99", 32)
if err := ix.FoldNow([]Parsed{manifest(late, "2026-08-01T10:00:00", []string{a})}); err != nil {
t.Fatal(err)
}
got, err = ix.TagsAdded()
if err != nil {
t.Fatal(err)
}
found := false
for _, g := range got {
if g.Checkin == late && g.Name == "elsewhere" && g.Src == x {
found = true
}
}
if len(got) != 4 || !found {
t.Errorf("FAIL: after its check-in arrived, TagsAdded = %+v; want the waiting tag listed too", got)
}
t.Log("ok: added sym tags on check-ins only, once on the carrier, branch names and raw tags excluded, a waiting tag listed when its check-in arrives")
}