Skip to content
quarry Artifact [b2c7e71037]
Login

Artifact [b2c7e71037]

Artifact b2c7e71037225985d59c26be7a4d7d41a0e13c1b2dce8f97e46ee70e27213e4c:


package index

import (
	"reflect"
	"testing"

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

func foldForum(t *testing.T, ix *Index, hash, root, parent, title, date string) {
	t.Helper()
	if err := ix.FoldNow([]Parsed{{Hash: hash, Art: &artifact.Artifact{
		Type: artifact.Forum, ThreadRoot: root, InReplyTo: parent,
		ThreadTitle: title, Date: date, User: "dan", Body: []byte("hi"),
	}}}); err != nil {
		t.Fatal(err)
	}
}

// Votes are keyed on full post hashes while pages link with prefixes;
// the resolver bridges the two and also answers which thread a post
// belongs to, so a vote row can carry its thread without trusting the
// form data.
func TestForumPostThreadResolvesPrefix(t *testing.T) {
	ix := openIndex(t)
	foldForum(t, ix, "aaaa1111", "", "", "hello", "2026-01-01T10:00:00")
	foldForum(t, ix, "bbbb2222", "aaaa1111", "aaaa1111", "", "2026-01-02T10:00:00")

	post, thread, ok, err := ix.ForumPostThread("bbbb")
	if err != nil || !ok {
		t.Fatalf("resolve reply: ok=%v err=%v", ok, err)
	}
	if post != "bbbb2222" || thread != "aaaa1111" {
		t.Fatalf("reply resolved to post=%q thread=%q", post, thread)
	}
	// An opener roots its own thread.
	if _, thread, _, _ := ix.ForumPostThread("aaaa"); thread != "aaaa1111" {
		t.Fatalf("opener thread = %q, want itself", thread)
	}
	if _, _, ok, err := ix.ForumPostThread("ffff"); ok || err != nil {
		t.Fatalf("absent prefix: ok=%v err=%v, want false nil", ok, err)
	}
}

func foldForumBody(t *testing.T, ix *Index, hash, root, title, date, body string) {
	t.Helper()
	if err := ix.FoldNow([]Parsed{{Hash: hash, Art: &artifact.Artifact{
		Type: artifact.Forum, ThreadRoot: root, ThreadTitle: title,
		Date: date, User: "dan", Body: []byte(body),
	}}}); err != nil {
		t.Fatal(err)
	}
}

// Anchors extract at fold and resolve only at query: the backlink query
// asks by the page's own full hash, so a stored ref surfaces exactly on
// the pages it prefixes — the honesty gate is structural.
func TestThreadsMentioning(t *testing.T) {
	ix := openIndex(t)
	const target = "4bd6301f22bd6301f22bd6301f22bd63"
	foldForumBody(t, ix, "aaaa1111", "", "Broken build", "2026-01-01T10:00:00",
		"bisected to [4bd6301f22] which landed yesterday")
	foldForumBody(t, ix, "bbbb2222", "", "Naming decade debate", "2026-01-02T10:00:00",
		"a decade of efface and facade — hexy words, no digits")
	foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-03T10:00:00",
		"also see deadbeef99 maybe")

	threads, err := ix.ThreadsMentioning(target)
	if err != nil {
		t.Fatal(err)
	}
	if len(threads) != 1 || threads[0].Root != "aaaa1111" {
		t.Fatalf("mentions of %s = %+v, want thread aaaa1111", target, threads)
	}
	if threads[0].Title != "Broken build" || threads[0].Posts != 2 {
		t.Fatalf("backlink row = %+v", threads[0])
	}
	// Hex-spelling English never anchors: no digits, no ref.
	if got, _ := ix.ThreadsMentioning("decade00decade00decade00"); len(got) != 0 {
		t.Fatalf("digit-free word anchored: %+v", got)
	}
	// A ref that prefixes nothing on this page stays silent.
	if got, _ := ix.ThreadsMentioning("ffffffffffffffff"); len(got) != 0 {
		t.Fatalf("unrelated hash has mentions: %+v", got)
	}
	// Re-folding the same post must not duplicate its anchors.
	foldForumBody(t, ix, "aaaa1111", "", "Broken build", "2026-01-01T10:00:00",
		"bisected to [4bd6301f22] which landed yesterday")
	if got, _ := ix.ThreadsMentioning(target); len(got) != 1 {
		t.Fatalf("re-fold duplicated anchors: %+v", got)
	}
}

// A forum edit is a new artifact whose P card names the version it
// replaces. The thread shows one post per chain — the newest text in
// the original's place — and replies made to an older version still
// hang off it. The history has every version, oldest first.
func TestForumEditShowsOnlyTheLatestVersion(t *testing.T) {
	ix := openIndex(t)
	fold := func(hash, root, parent, prior, body, date string) {
		t.Helper()
		var parents []string
		if prior != "" {
			parents = []string{prior}
		}
		if err := ix.FoldNow([]Parsed{{Hash: hash, Art: &artifact.Artifact{
			Type: artifact.Forum, ThreadRoot: root, InReplyTo: parent, Parents: parents,
			Date: date, User: "dan", Body: []byte(body),
		}}}); err != nil {
			t.Fatal(err)
		}
	}
	fold("aaaa1111", "", "", "", "opener", "2026-01-01T10:00:00")
	fold("bbbb2222", "aaaa1111", "aaaa1111", "", "first draft", "2026-01-02T10:00:00")
	fold("cccc3333", "aaaa1111", "bbbb2222", "", "a reply to the draft", "2026-01-03T10:00:00")
	fold("dddd4444", "aaaa1111", "aaaa1111", "bbbb2222", "second draft", "2026-01-04T10:00:00")
	fold("eeee5555", "aaaa1111", "aaaa1111", "dddd4444", "final", "2026-01-05T10:00:00")

	_, posts, ok, err := ix.ForumThreadPosts("aaaa")
	if err != nil || !ok {
		t.Fatalf("thread: ok=%v err=%v", ok, err)
	}
	var hashes []string
	for _, p := range posts {
		hashes = append(hashes, p.Hash)
	}
	// Three posts, not five: the chain shows once, in the ORIGINAL's
	// place (before the reply written on day 3).
	if want := []string{"aaaa1111", "eeee5555", "cccc3333"}; !reflect.DeepEqual(hashes, want) {
		t.Fatalf("thread shows %v, want %v", hashes, want)
	}
	final := posts[1]
	if final.Body != "final" || final.Edits != 2 || final.Edited != "2026-01-05T10:00:00" || final.Date != "2026-01-02T10:00:00" {
		t.Errorf("folded post = %+v", final)
	}
	if posts[2].Parent != "eeee5555" {
		t.Errorf("the reply to the first draft points at %q, want the newest version", posts[2].Parent)
	}
	if posts[0].Edits != 0 || posts[0].Edited != "" {
		t.Errorf("an unedited post claims edits: %+v", posts[0])
	}

	// History from ANY version's prefix, oldest first.
	for _, from := range []string{"bbbb", "dddd", "eeee"} {
		vs, ok, err := ix.ForumPostVersions(from)
		if err != nil || !ok {
			t.Fatalf("versions(%s): ok=%v err=%v", from, ok, err)
		}
		var got []string
		for _, v := range vs {
			got = append(got, v.Hash)
		}
		if want := []string{"bbbb2222", "dddd4444", "eeee5555"}; !reflect.DeepEqual(got, want) {
			t.Errorf("versions(%s) = %v, want %v", from, got, want)
		}
	}
	if vs, _, _ := ix.ForumPostVersions("cccc"); len(vs) != 1 {
		t.Errorf("an unedited post has %d versions, want 1", len(vs))
	}
}