Skip to content
quarry Artifact [46a0165aca]
Login

Artifact [46a0165aca]

Artifact 46a0165acad7cb79fa90a929dea404434f2db5bcf22e794577c34879d146c46e:


package index

import (
	"testing"

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

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

// Fossil deletes a wiki page by writing an edit with an EMPTY body.
// A page whose newest edit is empty must vanish from the list and the
// count — while its history stays fully readable, because the edits are
// real artifacts and a deleted page is still part of the record. And a
// page deleted then re-created is simply live again: the newest edit
// decides, not any edit.
func TestDeletedWikiPagesLeaveTheListButKeepTheirHistory(t *testing.T) {
	ix := openIndex(t)
	foldWiki(t, ix, "w1", "Guide", "2026-01-01T10:00:00", "how to")
	foldWiki(t, ix, "w2", "Scratch", "2026-01-02T10:00:00", "notes")
	foldWiki(t, ix, "w3", "Scratch", "2026-01-03T10:00:00", "") // deleted
	foldWiki(t, ix, "w4", "Phoenix", "2026-01-04T10:00:00", "v1")
	foldWiki(t, ix, "w5", "Phoenix", "2026-01-05T10:00:00", "")   // deleted...
	foldWiki(t, ix, "w6", "Phoenix", "2026-01-06T10:00:00", "v2") // ...and back

	titles, err := ix.WikiTitles()
	if err != nil {
		t.Fatal(err)
	}
	var names []string
	for _, w := range titles {
		names = append(names, w.Title)
	}
	if len(names) != 2 || names[0] != "Guide" || names[1] != "Phoenix" {
		t.Fatalf("live pages = %v, want [Guide Phoenix]: newest edit decides", names)
	}

	// The count sits above the list and must agree with it — a count
	// that includes what the list hides is a confirmation leak in
	// miniature and simply wrong here.
	c, err := ix.Counts()
	if err != nil {
		t.Fatal(err)
	}
	if c.WikiPages != 2 {
		t.Fatalf("count = %d, want 2 (the list's own rule)", c.WikiPages)
	}

	// History intact: the deleted page's revisions still resolve.
	if edits, err := ix.WikiHistory("Scratch"); err != nil || len(edits) != 2 {
		t.Fatalf("deleted page's history: %d edits, err=%v; the record must survive deletion", len(edits), err)
	}
	if rev, ok, err := ix.WikiRevision("Scratch", ""); err != nil || !ok || len(rev.Body) != 0 {
		t.Fatalf("deleted page's latest revision unreadable (ok=%v err=%v)", ok, err)
	}
	old, ok, err := ix.WikiRevision("Scratch", "w2")
	if err != nil || !ok || string(old.Body) != "notes" {
		t.Fatalf("deleted page's old content unreadable (ok=%v err=%v body=%q)", ok, err, old.Body)
	}
}

// A wiki's two maintenance questions, which listing its pages cannot
// answer: what can nobody reach, and what does it promise but not have.
func TestWikiLinksFindsOrphansAndWantedPages(t *testing.T) {
	ix := openIndex(t)
	foldWiki(t, ix, "w1", "Home", "2026-01-01T10:00:00", "See [[Guide]] and [[Missing]].")
	foldWiki(t, ix, "w2", "Guide", "2026-01-02T10:00:00", "Back to [[Home]], also [[Missing]].")
	foldWiki(t, ix, "w3", "Lonely", "2026-01-03T10:00:00", "Nothing points here. [[Lonely]].")
	foldWiki(t, ix, "w4", "Gone", "2026-01-04T10:00:00", "[[Guide]]")
	foldWiki(t, ix, "w5", "Gone", "2026-01-05T10:00:00", "") // deleted

	st, err := ix.WikiLinks("Home", markup.WikiRefs)
	if err != nil {
		t.Fatal(err)
	}
	// Home is where a reader starts, so nothing needs to point at it;
	// a page that links only to itself is still unreachable.
	if len(st.Orphans) != 1 || st.Orphans[0] != "Lonely" {
		t.Fatalf("orphans = %v, want [Lonely]: Home is the entrance and a self-link is not an inbound link", st.Orphans)
	}
	// Two live pages ask for Missing; the deleted page's link to Guide
	// counts for nothing, because a deleted page is not live text.
	if len(st.Wanted) != 1 || st.Wanted[0].Title != "Missing" || st.Wanted[0].Links != 2 {
		t.Fatalf("wanted = %+v, want Missing asked for twice", st.Wanted)
	}
}

// Fossil renders [Name] as a link only when the page exists and leaves
// it as prose otherwise. Reading it as a request for a page filled the
// wanted list of fossil's own wiki with shell fragments and hashes —
// ordinary sentences that happen to sit in square brackets.
func TestFossilBracketsAreProseUntilTheyResolve(t *testing.T) {
	ix := openIndex(t)
	foldWiki(t, ix, "w1", "Home", "2026-01-01T10:00:00",
		"Run [ ! -z \"$text\" ] and see [Guide] and [[Wanted]].")
	foldWiki(t, ix, "w2", "Guide", "2026-01-02T10:00:00", "content")

	st, err := ix.WikiLinks("Home", markup.WikiRefs)
	if err != nil {
		t.Fatal(err)
	}
	// [Guide] resolves, so Guide is reached and not an orphan.
	for _, o := range st.Orphans {
		if o == "Guide" {
			t.Error("a resolving [Guide] did not count as an inbound link")
		}
	}
	// Only the double-bracket form asks for a page that is not there.
	if len(st.Wanted) != 1 || st.Wanted[0].Title != "Wanted" {
		t.Fatalf("wanted = %+v, want only the [[Wanted]] request", st.Wanted)
	}
}