Skip to content
quarry Artifact [d38893695c]
Login

Artifact [d38893695c]

Artifact d38893695c4b9dfb0cd61180b97f7a9bfe92e808564a8e4800acbc349facf7da:


package index

import (
	"testing"
)

// RFC 0007: the merge is the fact and the reply is the claim. A reply
// that says "accepted" with nothing merged leaves the record OPEN and
// records the claim; the merge check-in alone, with no reply at all,
// settles it. Unconditional — no policy row is consulted — which is
// what lets a wipe-and-reingest, or a protection row added later,
// change nothing about how history renders.
func TestAcceptReplyWithoutAMergeIsAClaim(t *testing.T) {
	ix := openIndex(t)
	landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "Ship it")
	foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00",
		landingState("accepted", "I say so"))

	l := onlyOne(t, ix, "aaaa1111")
	if l.State != "open" {
		t.Fatalf("state %q after a reply-only accept, want open", l.State)
	}
	if l.ClaimedBy != "bbbb2222" {
		t.Errorf("claimed_by %q, want the reply that made the claim", l.ClaimedBy)
	}
	if l.SettledBy != "" {
		t.Errorf("settled_by %q on an unsettled record", l.SettledBy)
	}
	// On the timeline it is conversation, not a verdict.
	if k, _, c := eventRow(t, ix, "bbbb2222"); k != "lc" || c != "Acceptance claimed on landing: Ship it" {
		t.Errorf("the claim is filed as %q/%q", k, c)
	}
}

func TestAMergeWithNoReplySettlesTheRecord(t *testing.T) {
	ix := openIndex(t)
	ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base")
	landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "Ship it")
	ci(t, ix, "f1", "feat-x", "2026-01-01T11:00:00", "work", "t1")
	// An owner merged from a clone and pushed; nobody posted a reply.
	ci(t, ix, "m1", "trunk", "2026-01-02T10:00:00", "land feat-x", "t1", "f1")
	// The check-ins arrived AFTER the landing was folded, so the
	// record was open until something re-read it. The refresh after
	// recomputeBranches is that something.
	tx, err := ix.db.Begin()
	if err != nil {
		t.Fatal(err)
	}
	if err := refreshLandingStates(tx); err != nil {
		t.Fatal(err)
	}
	if err := tx.Commit(); err != nil {
		t.Fatal(err)
	}
	l := onlyOne(t, ix, "aaaa1111")
	if l.State != "accepted" {
		t.Fatalf("state %q with the merge in the DAG, want accepted", l.State)
	}
	if l.SettledBy != "" || l.ClaimedBy != "" {
		t.Errorf("settled_by=%q claimed_by=%q, want both empty: nothing was posted", l.SettledBy, l.ClaimedBy)
	}
	// And the approver named on the timeline is whoever wrote the merge.
	rows := []TimelineEntry{{Kind: "ci", Hash: "m1", IsMerge: true}}
	if err := ix.attachLandings(rows); err != nil {
		t.Fatal(err)
	}
	if rows[0].LandingApprover != "dan" {
		t.Errorf("approver %q, want the merge's author", rows[0].LandingApprover)
	}
}

// A claim, then the merge: the claim clears and the record settles.
// The reply that claimed becomes the settling reply, because it was
// right — just early.
func TestAClaimBecomesAVerdictWhenTheMergeArrives(t *testing.T) {
	ix := openIndex(t)
	ci(t, ix, "t1", "trunk", "2026-01-01T09:00:00", "base")
	landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "Ship it")
	ci(t, ix, "f1", "feat-x", "2026-01-01T11:00:00", "work", "t1")
	foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00",
		landingState("accepted", "landing"))
	if l := onlyOne(t, ix, "aaaa1111"); l.State != "open" || l.ClaimedBy != "bbbb2222" {
		t.Fatalf("precondition: %+v", l)
	}
	ci(t, ix, "m1", "trunk", "2026-01-02T10:00:05", "land feat-x", "t1", "f1")
	// Any later post re-folds the thread; a comment will do.
	foldForumBody(t, ix, "cccc3333", "aaaa1111", "", "2026-01-02T11:00:00", "nice")
	l := onlyOne(t, ix, "aaaa1111")
	if l.State != "accepted" || l.SettledBy != "bbbb2222" || l.ClaimedBy != "" {
		t.Fatalf("after the merge: %+v, want accepted, settled by the reply, no claim", l)
	}
	if k, _, c := eventRow(t, ix, "bbbb2222"); k != "l" || c != "Landing accepted: Ship it" {
		t.Errorf("the reply is still filed as a claim: %q/%q", k, c)
	}
}

// Rejection has no DAG fact to read, so the reply stays the record.
func TestRejectionIsStillTheReplysToSay(t *testing.T) {
	ix := openIndex(t)
	landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "No thanks")
	foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00",
		landingState("rejected", "not this"))
	if l := onlyOne(t, ix, "aaaa1111"); l.State != "rejected" || l.SettledBy != "bbbb2222" {
		t.Fatalf("%+v, want rejected by the reply", l)
	}
}

// The merge must be the landing's own: a merge of the branch from
// BEFORE the landing opened is history the proposal was made on top
// of, not the proposal landing.
func TestAMergeBeforeTheLandingOpenedDoesNotCount(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", "old work", "t1")
	ci(t, ix, "m0", "trunk", "2026-01-01T09:45:00", "landed once already", "t1", "f1")
	landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "Round two")
	if l := onlyOne(t, ix, "aaaa1111"); l.State != "open" {
		t.Fatalf("state %q — an earlier merge settled a later proposal", l.State)
	}
}

// The branch is already on the target — merged by hand before the
// landing was opened, or the target simply contains its tip. zit
// settles such a landing as accepted with nothing to merge; the fold
// must read the same, by ancestry, rather than call the reply a claim.
func TestAcceptOfAContainedBranchIsAcceptedNotAClaim(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")
	// trunk already carries f1 (a merge from before the landing).
	ci(t, ix, "t2", "trunk", "2026-01-01T09:45:00", "carried in", "t1", "f1")
	landingOn(t, ix, "aaaa1111", "feat-x", "2026-01-01T10:00:00", "Already there")
	foldForumBody(t, ix, "bbbb2222", "aaaa1111", "", "2026-01-02T10:00:00",
		landingState("accepted", "nothing to land"))
	l := onlyOne(t, ix, "aaaa1111")
	if l.State != "accepted" || l.SettledBy != "bbbb2222" || l.ClaimedBy != "" {
		t.Fatalf("contained branch: %+v, want accepted by the reply, no claim", l)
	}
	// A reply-only accept with the branch NOT contained is still a claim.
	ci(t, ix, "g1", "feat-y", "2026-01-01T09:30:00", "other work", "t1")
	landingOn(t, ix, "cccc3333", "feat-y", "2026-01-01T10:00:00", "Not there")
	foldForumBody(t, ix, "dddd4444", "cccc3333", "", "2026-01-02T10:00:00",
		landingState("accepted", "I say so"))
	if l := onlyOne(t, ix, "cccc3333"); l.State != "open" || l.ClaimedBy != "dddd4444" {
		t.Fatalf("uncontained reply-only accept: %+v, want a claim", l)
	}
}