Skip to content
quarry Artifact [908929701e]
Login

Artifact [908929701e]

Artifact 908929701e703ced964c10ce24977976fdfe275df317337c00e20c24f6f2a3c2:


package index

import (
	"testing"
)

// A landing record's root body, as zit mints it (zit#127).
func landingBody(branch, target, msg string) string {
	return "zit-landing: 1\nbranch: " + branch + "\ntarget: " + target + "\n\n" + msg + "\n"
}

func landingState(state, msg string) string {
	return "zit-landing-state: " + state + "\n\n" + msg + "\n"
}

func onlyLanding(t *testing.T, ix *Index, state string) LandingSummary {
	t.Helper()
	got, err := ix.Landings(state, 0)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 1 {
		t.Fatalf("Landings(%q) returned %d rows, want 1: %+v", state, len(got), got)
	}
	return got[0]
}

func TestLandingRootFoldsIntoARecord(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "land the x work", "2026-01-01T10:00:00",
		landingBody("feat/x", "trunk", "ready for review"))

	l := onlyLanding(t, ix, "")
	if l.Branch != "feat/x" || l.Target != "trunk" {
		t.Fatalf("branch/target = %q/%q, want feat/x/trunk", l.Branch, l.Target)
	}
	if l.State != "open" {
		t.Fatalf("state = %q, want open — a record with no transition is open by absence", l.State)
	}
	if l.Title != "land the x work" || l.Author != "dan" {
		t.Fatalf("title/author = %q/%q", l.Title, l.Author)
	}
	if l.SettledBy != "" {
		t.Fatalf("settled_by = %q, want empty while open", l.SettledBy)
	}
}

func TestLandingStateIsLatestTransitionWins(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00",
		landingBody("feat/x", "trunk", "ready"))
	// A plain comment must not settle anything.
	foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-01T11:00:00", "looks good\n")
	if l := onlyLanding(t, ix, ""); l.State != "open" {
		t.Fatalf("a comment settled the record: state = %q", l.State)
	}

	foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-02T10:00:00",
		landingState("rejected", "not yet"))
	if l := onlyLanding(t, ix, ""); l.State != "rejected" || l.SettledBy != "cccc3333" {
		t.Fatalf("state/settled_by = %q/%q, want rejected/cccc3333", l.State, l.SettledBy)
	}

	// Later wins, so a reopened-then-accepted thread reads accepted.
	// RFC 0007: the merge is the fact. The accept reply settles the
	// record because the DAG holds the merge it announces.
	ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base")
	ci(t, ix, "f1", "feat/x", "2026-01-01T09:30:00", "work", "t1")
	ci(t, ix, "m1", "trunk", "2026-01-03T09:59:00", "land feat/x", "t1", "f1")
	foldForumBody(t, ix, "dddd4444", "aaaa1111", "", "2026-01-03T10:00:00",
		landingState("accepted", "landed"))
	l := onlyLanding(t, ix, "")
	if l.State != "accepted" || l.SettledBy != "dddd4444" {
		t.Fatalf("state/settled_by = %q/%q, want accepted/dddd4444", l.State, l.SettledBy)
	}
	if l.Updated != "2026-01-03T10:00:00" {
		t.Fatalf("updated = %q, want the newest post's date", l.Updated)
	}
	if l.Opened != "2026-01-01T10:00:00" {
		t.Fatalf("opened = %q, want the root's date", l.Opened)
	}
}

// The fold must not depend on arrival order: sync delivers artifacts in
// store order, which is not thread order, and a reply can be folded before
// the root it belongs to.
func TestLandingFoldIsOrderIndependent(t *testing.T) {
	ix := openIndex(t)
	ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base")
	ci(t, ix, "f1", "feat/x", "2026-01-01T09:30:00", "work", "t1")
	ci(t, ix, "m1", "trunk", "2026-01-02T09:59:00", "land feat/x", "t1", "f1")
	foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-02T10:00:00",
		landingState("accepted", "landed"))
	// The root has not arrived; nothing can be known yet, and that must not
	// be an error or a half-written row.
	if got, err := ix.Landings("", 0); err != nil || len(got) != 0 {
		t.Fatalf("Landings before the root: %d rows, err=%v — want 0, nil", len(got), err)
	}

	foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00",
		landingBody("feat/x", "trunk", "ready"))
	l := onlyLanding(t, ix, "")
	if l.State != "accepted" || l.SettledBy != "cccc3333" {
		t.Fatalf("out-of-order fold converged on %q/%q, want accepted/cccc3333", l.State, l.SettledBy)
	}
}

func TestOrdinaryForumThreadIsNotALanding(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "Broken build", "2026-01-01T10:00:00",
		"the build is broken on aarch64\n")
	foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-01T11:00:00", "fixed\n")
	got, err := ix.Landings("", 0)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 0 {
		t.Fatalf("an ordinary thread indexed as %d landing(s): %+v", len(got), got)
	}
}

// Refusing to guess: a header line this code does not know means the body
// speaks a dialect we only think we understand, so it is not read at all.
func TestLandingWithUnknownHeaderLineIsRefused(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "odd", "2026-01-01T10:00:00",
		"zit-landing: 1\nbranch: feat/x\ntarget: trunk\nreviewers: someone\n\nbody\n")
	if got, _ := ix.Landings("", 0); len(got) != 0 {
		t.Fatalf("an unknown header line was indexed anyway: %+v", got)
	}

	// Same for a missing required field.
	foldForumBody(t, ix, "bbbb2222", "", "odd", "2026-01-01T10:00:00",
		"zit-landing: 1\nbranch: feat/x\n\nbody\n")
	if got, _ := ix.Landings("", 0); len(got) != 0 {
		t.Fatalf("a record with no target was indexed: %+v", got)
	}
}

// An unknown state must never settle a record — a reply naming one is a
// comment with an odd first line.
func TestUnknownLandingStateDoesNotSettle(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00",
		landingBody("feat/x", "trunk", "ready"))
	foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00",
		"zit-landing-state: merged\n\nwhatever\n")
	if l := onlyLanding(t, ix, ""); l.State != "open" {
		t.Fatalf("an unknown state settled the record as %q", l.State)
	}
}

func TestLandingsFilterAndLookup(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "one", "2026-01-01T10:00:00",
		landingBody("feat/x", "trunk", "ready"))
	foldForumBody(t, ix, "bbbb2222", "", "two", "2026-01-02T10:00:00",
		landingBody("feat/y", "trunk", "ready"))
	ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base")
	ci(t, ix, "g1", "feat/y", "2026-01-02T11:00:00", "work", "t1")
	ci(t, ix, "m2", "trunk", "2026-01-03T09:59:00", "land feat/y", "t1", "g1")
	foldForumBody(t, ix, "cccc3333", "bbbb2222", "", "2026-01-03T10:00:00",
		landingState("accepted", "landed"))

	open, err := ix.Landings("open", 0)
	if err != nil {
		t.Fatal(err)
	}
	if len(open) != 1 || open[0].Branch != "feat/x" {
		t.Fatalf("open landings = %+v, want just feat/x", open)
	}
	all, err := ix.Landings("", 0)
	if err != nil {
		t.Fatal(err)
	}
	if len(all) != 2 {
		t.Fatalf("all landings = %d, want 2", len(all))
	}
	// Newest activity first.
	if all[0].ID != "bbbb2222" {
		t.Fatalf("ordering put %q first, want bbbb2222 (newest updated)", all[0].ID)
	}

	l, ok, err := ix.Landing("bbbb2222")
	if err != nil || !ok || l.State != "accepted" {
		t.Fatalf("Landing(bbbb2222) = %+v ok=%v err=%v", l, ok, err)
	}
	if _, ok, err := ix.Landing("nope"); ok || err != nil {
		t.Fatalf("Landing(absent) = ok=%v err=%v, want false nil", ok, err)
	}
}

// An index whose forum_post rows predate this table must not show an empty
// landing list until someone reindexes — the same guarantee backfillTickets
// makes, and for the same reason.
func TestBackfillLandingsPopulatesAnUpgradedIndex(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00",
		landingBody("feat/x", "trunk", "ready"))
	ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base")
	ci(t, ix, "f1", "feat/x", "2026-01-01T11:00:00", "work", "t1")
	ci(t, ix, "m1", "trunk", "2026-01-02T09:59:00", "land feat/x", "t1", "f1")
	foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00",
		landingState("accepted", "landed"))

	// Simulate the upgrade: the posts are indexed, the table is not.
	if _, err := ix.db.Exec(`DELETE FROM landing`); err != nil {
		t.Fatal(err)
	}
	if got, _ := ix.Landings("", 0); len(got) != 0 {
		t.Fatal("setup failed: landing table not empty")
	}
	if err := ix.backfillLandings(); err != nil {
		t.Fatal(err)
	}
	l := onlyLanding(t, ix, "")
	if l.Branch != "feat/x" || l.State != "accepted" {
		t.Fatalf("backfilled %+v, want feat/x accepted", l)
	}

	// A populated table is left alone rather than recomputed at every open.
	if _, err := ix.db.Exec(`UPDATE landing SET branch = 'sentinel'`); err != nil {
		t.Fatal(err)
	}
	if err := ix.backfillLandings(); err != nil {
		t.Fatal(err)
	}
	if l := onlyLanding(t, ix, ""); l.Branch != "sentinel" {
		t.Fatal("backfill overwrote a populated table")
	}
}

// A thread that stops being a landing must not leave a record behind.
func TestLandingRowIsDroppedWhenTheRootIsNotALanding(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00",
		landingBody("feat/x", "trunk", "ready"))
	if got, _ := ix.Landings("", 0); len(got) != 1 {
		t.Fatal("setup failed")
	}
	// Refolding the same hash with an ordinary body (a corrected mirror, a
	// shunned-and-replaced record) must retract the row.
	foldForumBody(t, ix, "aaaa1111", "", "land it", "2026-01-01T10:00:00", "never mind\n")
	if got, _ := ix.Landings("", 0); len(got) != 0 {
		t.Fatalf("a retracted record survived: %+v", got)
	}
}

// A PARTIALLY populated table is healed, not left alone.
//
// The old guard was `landings > 0`, which reads "has any row" as "is
// complete". Once one landing existed the recovery never ran again, so
// a thread the one-shot did not see was never seen — and nothing said
// so, because the list answered successfully with the rows it did have.
//
// On this forge two open landings sat invisible for nineteen and nine
// days: state=open answered 0 while the DAG held both, and the rows
// only appeared when somebody wrote to the threads and the ordinary
// fold picked them up. The operator's report was "I don't see those
// landings to reject" (bbbc0a4da2e5).
func TestBackfillLandingsHealsAPartiallyPopulatedTable(t *testing.T) {
	ix := openIndex(t)
	ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base")
	// Two landing threads, both fully indexed as posts.
	foldForumBody(t, ix, "aaaa1111", "", "land one", "2026-01-01T10:00:00",
		landingBody("feat/one", "trunk", "ready"))
	foldForumBody(t, ix, "cccc3333", "", "land two", "2026-01-02T10:00:00",
		landingBody("feat/two", "trunk", "ready"))
	if got, _ := ix.Landings("", 0); len(got) != 2 {
		t.Fatalf("setup: %d landings, want 2", len(got))
	}

	// The state the bug leaves behind: one row present, one absent, and
	// a table that is not empty so the old guard declined to look.
	if _, err := ix.db.Exec(`DELETE FROM landing WHERE id = 'cccc3333'`); err != nil {
		t.Fatal(err)
	}
	if got, _ := ix.Landings("", 0); len(got) != 1 {
		t.Fatalf("setup: %d landings, want 1", len(got))
	}

	if err := ix.backfillLandings(); err != nil {
		t.Fatal(err)
	}
	got, err := ix.Landings("", 0)
	if err != nil {
		t.Fatal(err)
	}
	if len(got) != 2 {
		t.Fatalf("FAIL: %d landings after backfill, want 2 — a missing row stayed missing "+
			"because the table was not empty", len(got))
	}
	var healed bool
	for _, l := range got {
		if l.ID == "cccc3333" && l.Branch == "feat/two" {
			healed = true
		}
	}
	if !healed {
		t.Fatalf("FAIL: the absent landing was not restored: %+v", got)
	}
	t.Log("ok: a table with a hole in it is filled, not declared complete")
}

// And the healing must not disturb rows that are already there, which
// is what the empty-table guard was really protecting.
func TestBackfillLandingsLeavesExistingRowsUntouched(t *testing.T) {
	ix := openIndex(t)
	ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base")
	foldForumBody(t, ix, "aaaa1111", "", "land one", "2026-01-01T10:00:00",
		landingBody("feat/one", "trunk", "ready"))
	foldForumBody(t, ix, "cccc3333", "", "land two", "2026-01-02T10:00:00",
		landingBody("feat/two", "trunk", "ready"))
	if _, err := ix.db.Exec(`DELETE FROM landing WHERE id = 'cccc3333'`); err != nil {
		t.Fatal(err)
	}
	// A sentinel the fold would overwrite if the backfill recomputed
	// everything rather than only what is absent.
	if _, err := ix.db.Exec(`UPDATE landing SET branch = 'sentinel' WHERE id = 'aaaa1111'`); err != nil {
		t.Fatal(err)
	}
	if err := ix.backfillLandings(); err != nil {
		t.Fatal(err)
	}
	for _, l := range mustLandings(t, ix) {
		if l.ID == "aaaa1111" && l.Branch != "sentinel" {
			t.Fatalf("FAIL: an existing row was recomputed (branch %q) — healing a hole must not "+
				"become a rebuild", l.Branch)
		}
	}
	t.Log("ok: present rows are left exactly as they were")
}

func mustLandings(t *testing.T, ix *Index) []LandingSummary {
	t.Helper()
	got, err := ix.Landings("", 0)
	if err != nil {
		t.Fatal(err)
	}
	return got
}

// READING ONE LANDING AND READING THE LIST MUST DESCRIBE IT THE SAME WAY.
//
// landing.list derived the reply count from forum_post and landing.get
// did not derive it at all, so a thread with three remarks showed three
// in the list and none on the record — the same object, two answers,
// and no way for a reader to tell which surface was lying. That is the
// identical shape to repo.get's contributors, which read one of its two
// numbers and left the other at the zero value.
//
// The count's DEFINITION is pinned here too, because the two surfaces
// agreeing on a wrong rule would pass an agreement test: a landing's
// state transition is an event, not something anyone said, and the
// opener is not a remark on their own proposal.
func TestLandingGetAndLandingListAgreeAboutReplies(t *testing.T) {
	ix := openIndex(t)
	foldForumBody(t, ix, "aaaa1111", "", "land the x work", "2026-01-01T10:00:00",
		landingBody("feat/x", "trunk", "ready for review"))
	// Two remarks, and one state transition that is not a remark.
	foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-01T11:00:00",
		"looks right to me")
	foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-01T12:00:00",
		"one nit, otherwise good")
	foldForumBody(t, ix, "dddd4444", "aaaa1111", "", "2026-01-02T09:00:00",
		landingState("accepted", "landed"))

	listed := onlyLanding(t, ix, "")
	if listed.Replies != 2 {
		t.Errorf("the listing says replies = %d, want 2 (two remarks; the opener and the "+
			"state transition are neither)", listed.Replies)
	}

	one, ok, err := ix.Landing("aaaa1111")
	if err != nil || !ok {
		t.Fatalf("Landing(aaaa1111) ok=%v err=%v", ok, err)
	}
	if one.Replies != listed.Replies {
		t.Errorf("landing.get says replies = %d and landing.list says %d about the same "+
			"landing: a record and its row must not disagree about what was said on it",
			one.Replies, listed.Replies)
	}
	if one.Replies != 2 {
		t.Errorf("landing.get says replies = %d, want 2", one.Replies)
	}
}