Artifact 1365bd8fe1c42744abbd5683a13e21c29eee113717cd3bb3cc9992e4e992d87b:
- File
internal/index/landing_test.go
— part of check-in
[3c6bee847a]
at
2026-08-23 13:42:55
on branch trunk
— index: fold landing records into a queryable surface
A landing (zit#127) is a reading of forum_post, not a new artifact kind, so the table is derived and recomputed whenever any post in its thread folds -- which is what makes a reply indexed before its root converge on the same row. Backfilled once at open for indexes whose posts predate the table. (user: dmestas, size: 8471) [annotate] [blame] [check-ins using]
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. 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) 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")) 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")) 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) } }