Skip to content
quarry Artifact [23fafd5302]
Login

Artifact [23fafd5302]

Artifact 23fafd5302450eba4220d926a95bf32f7615f6ac9cb7e7cdf61a52073d7a59a2:


package index

import (
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/artifact"
)

// Fossil tags are a LOG, not a set: a later artifact can cancel an
// earlier one. TagsOf and the timeline's tag filter must agree on which
// tags are standing, or a chip on the check-in page links to a timeline
// that disagrees with the chip.
func TestTagsOfAndTimelineFilterAgreeOnStandingTags(t *testing.T) {
	ix := openIndex(t)
	a, b := strings.Repeat("a1", 32), strings.Repeat("b2", 32)
	ci := func(hash, date string, tags ...artifact.Tag) Parsed {
		return Parsed{Hash: hash, Art: &artifact.Artifact{
			Type: artifact.Manifest, Date: date, User: "dan",
			Comment: "c", Tags: tags}}
	}
	if err := ix.FoldNow([]Parsed{
		ci(a, "2026-08-18T10:00:00",
			artifact.Tag{Name: "sym-release", Target: "*"},
			artifact.Tag{Name: "sym-version-1.0", Target: "*"}),
		ci(b, "2026-08-19T10:00:00",
			artifact.Tag{Name: "sym-doomed", Target: "*"}),
	}); err != nil {
		t.Fatal(err)
	}

	got, err := ix.TagsOf(a)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 2 || got[0] != "release" || got[1] != "version-1.0" {
		t.Fatalf("TagsOf = %v, want [release version-1.0] sorted", got)
	}

	// The filter finds exactly the check-in the chip sits on.
	tl, _, err := ix.Timeline(TimelineFilter{Limit: 10, Tag: "release"})
	if err != nil {
		t.Fatal(err)
	}
	if len(tl) != 1 || tl[0].Hash != a {
		t.Fatalf("tag filter returned %d rows, want just %.4s", len(tl), a)
	}

	// A tag nobody carries is an empty timeline, not an error and not
	// the whole timeline — the failure mode that would matter most.
	if tl, _, err := ix.Timeline(TimelineFilter{Limit: 10, Tag: "nonesuch"}); err != nil || len(tl) != 0 {
		t.Fatalf("unknown tag returned %d rows (err %v)", len(tl), err)
	}

	// Cancel one. A later artifact carrying the minus sign is fossil's
	// way of retracting a tag, and both readers must stop reporting it.
	if err := ix.FoldNow([]Parsed{
		{Hash: strings.Repeat("c3", 32), Art: &artifact.Artifact{
			Type: artifact.Manifest, Date: "2026-08-20T10:00:00", User: "dan",
			Comment: "untag", Tags: []artifact.Tag{{Name: "-sym-doomed", Target: b}}}},
	}); err != nil {
		t.Fatal(err)
	}
	got, err = ix.TagsOf(b)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 0 {
		t.Errorf("TagsOf still reports a cancelled tag: %v", got)
	}
	if tl, _, err := ix.Timeline(TimelineFilter{Limit: 10, Tag: "doomed"}); err != nil || len(tl) != 0 {
		t.Errorf("the filter still finds a cancelled tag: %d rows (err %v)", len(tl), err)
	}
}

// Fossil lets an author correct a check-in comment after the fact with
// a `comment` tag, and the correction is what fossil displays. quarry
// showed the superseded C card instead — not an omission but a wrong
// answer, and 697 fossil check-ins are in that state.
func TestAttachCIAppliesCommentCorrectionsAndFlags(t *testing.T) {
	ix := openIndex(t)
	a := strings.Repeat("a1", 32)
	if err := ix.FoldNow([]Parsed{
		{Hash: a, Art: &artifact.Artifact{
			Type: artifact.Manifest, Date: "2026-08-18T10:00:00", User: "dan",
			Comment: "fixs the thing"}},
		{Hash: strings.Repeat("b2", 32), Art: &artifact.Artifact{
			Type: artifact.Manifest, Date: "2026-08-19T10:00:00", User: "dan",
			Comment: "amend", Tags: []artifact.Tag{
				{Name: "+comment", Target: a, Value: "fixes the thing"},
				{Name: "*hidden", Target: a},
			}}},
	}); err != nil {
		t.Fatal(err)
	}

	e, _, ok, err := ix.CheckinByPrefix(a)
	if err != nil || !ok {
		t.Fatalf("checkin not found (err %v)", err)
	}
	// Undecorated, the stored C card is untouched: it is an artifact
	// fact and the index must not rewrite it.
	if e.Comment != "fixs the thing" {
		t.Errorf("stored comment was rewritten to %q", e.Comment)
	}

	entries := []TimelineEntry{e}
	if err := ix.AttachCI(entries); err != nil {
		t.Fatal(err)
	}
	if entries[0].Comment != "fixes the thing" {
		t.Errorf("shown comment = %q, want the correction", entries[0].Comment)
	}
	if entries[0].CommentOrig != "fixs the thing" {
		t.Errorf("superseded comment = %q, want it kept", entries[0].CommentOrig)
	}
	if !entries[0].Hidden {
		t.Error("a standing hidden tag was not reported")
	}
	if entries[0].Closed {
		t.Error("closed was reported without a closed tag")
	}

	// A check-in nobody corrected must not claim it was corrected.
	plain := []TimelineEntry{{Hash: strings.Repeat("b2", 32), Comment: "amend"}}
	if err := ix.AttachCI(plain); err != nil {
		t.Fatal(err)
	}
	if plain[0].CommentOrig != "" {
		t.Errorf("uncorrected check-in reports an original of %q", plain[0].CommentOrig)
	}

	// The history is the whole log, with the superseded row marked.
	hist, err := ix.TagHistory(a)
	if err != nil {
		t.Fatal(err)
	}
	if len(hist) != 2 {
		t.Fatalf("history has %d rows, want 2: %+v", len(hist), hist)
	}
	for _, h := range hist {
		if !h.Standing {
			t.Errorf("row %+v should stand — nothing overruled it", h)
		}
	}
}

// A branch diff is the branch against its FORK POINT, which is not the
// same base a landing uses (the target's current tip). Getting this
// wrong shows the right file count against the wrong baseline.
func TestForkPointIsWhereTheBranchLeftItsParent(t *testing.T) {
	ix := openIndex(t)
	root, mid, br1, br2 := strings.Repeat("a1", 32), strings.Repeat("b2", 32),
		strings.Repeat("c3", 32), strings.Repeat("d4", 32)
	ci := func(hash, date string, tags []artifact.Tag, parents ...string) Parsed {
		return Parsed{Hash: hash, Art: &artifact.Artifact{
			Type: artifact.Manifest, Date: date, User: "dan",
			Comment: "c", Parents: parents, Tags: tags}}
	}
	onto := func(name string) []artifact.Tag {
		return []artifact.Tag{{Name: "*branch", Target: "*", Value: name},
			{Name: "*sym-" + name, Target: "*"}}
	}
	if err := ix.FoldNow([]Parsed{
		ci(root, "2026-08-01T10:00:00", nil),
		ci(mid, "2026-08-02T10:00:00", nil, root),
		ci(br1, "2026-08-03T10:00:00", onto("feature"), mid),
		ci(br2, "2026-08-04T10:00:00", nil, br1),
	}); err != nil {
		t.Fatal(err)
	}

	// The fork is where the branch left trunk — mid, not the trunk tip
	// and not the branch's own first check-in.
	got, err := ix.ForkPoint("feature")
	if err != nil {
		t.Fatal(err)
	}
	if got != mid {
		t.Errorf("fork point = %.4s, want %.4s", got, mid)
	}

	// The branch's own tip is its newest member, not the repository's.
	tip, ok, err := ix.TipOf("feature")
	if err != nil || !ok {
		t.Fatalf("no tip for the branch (err %v)", err)
	}
	if tip.Tip != br2 {
		t.Errorf("tip = %.4s, want %.4s", tip.Tip, br2)
	}

	// A line that forked from nothing has no fork point, and must say
	// so rather than inventing one — the page uses this to decide
	// whether to offer the link at all.
	if got, err := ix.ForkPoint("trunk"); err != nil || got != "" {
		t.Errorf("trunk fork point = %q (err %v), want empty", got, err)
	}
	if got, err := ix.ForkPoint("no-such-branch"); err != nil || got != "" {
		t.Errorf("unknown branch = %q (err %v), want empty", got, err)
	}
}

// TagTarget is the check-in a tag NAME stands on: the newest one
// carrying it, whether or not it propagates, and never one where it was
// cancelled. An archive of tag:v1.0 is built from it.
func TestTagTargetFindsTheNewestStandingTag(t *testing.T) {
	ix := openIndex(t)
	a, b, c := strings.Repeat("a1", 32), strings.Repeat("b2", 32), strings.Repeat("c3", 32)
	if err := ix.FoldNow([]Parsed{
		{Hash: a, Art: &artifact.Artifact{Type: artifact.Manifest, Date: "2026-08-18T10:00:00", User: "dan", Comment: "a",
			Tags: []artifact.Tag{{Name: "+sym-v1.0", Target: "*"}}}},
		{Hash: b, Art: &artifact.Artifact{Type: artifact.Manifest, Date: "2026-08-19T10:00:00", User: "dan", Comment: "b",
			Tags: []artifact.Tag{{Name: "+sym-v1.0", Target: "*"}}}},
		{Hash: c, Art: &artifact.Artifact{Type: artifact.Manifest, Date: "2026-08-20T10:00:00", User: "dan", Comment: "untag b",
			Tags: []artifact.Tag{{Name: "-sym-v1.0", Target: b}}}},
	}); err != nil {
		t.Fatal(err)
	}
	got, ok, err := ix.TagTarget("v1.0")
	if err != nil || !ok || got != a {
		t.Fatalf("FAIL: TagTarget(v1.0) = %.8s %v %v, want %.8s: b's tag was cancelled", got, ok, err, a)
	}
	if _, ok, err := ix.TagTarget("nonesuch"); ok || err != nil {
		t.Fatalf("FAIL: an unknown tag answered %v %v", ok, err)
	}
	t.Log("ok")
}