Files
mesh-catalog/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/holder_test.go
T
jschoubben 30d04f6bef Pin the gate to mesh-sdk's release 6 verb tables and prove it holds the seat as they have it (issue 1946, R6-C3)
The integration tests hear the forge's own word that a pull request merged, as the real flow does, and fail on any call of the controller.
2026-10-24 16:35:15 +02:00

362 lines
14 KiB
Go

package main
import (
"encoding/json"
"strings"
"testing"
"time"
)
// The holder, end to end over fakes (novox/hq ADR 0239 *How it is checked*, cut by ADR 0539 §17): a pull request's head
// proposed and ready; merged; a newer head closing the older; a restart resuming; the trunk rule; and every transition
// kept before it is said, noted on the commit and shown on the pull request.
const (
head = "aaaaaaaaaaaa1111"
merge = "mmmmmmmmmmmm9999"
)
func TestAPullRequestsHeadIsProposedAndReady(t *testing.T) {
w := newWorld(t)
p := pr("novox/app", 7, head, "feat/x", "src/a.go")
p.Body = "" // an empty description is never read again, so the review set below stays
w.h.PullUpdated(p)
id := IDOf("novox/app", head)
// This head is not a full commit id, so its description line cannot name it: its review is set as read.
w.h.deliveries[id].ReviewedBy, w.h.deliveries[id].ReviewedAt = "tester@shanks", head
if w.state(id) != Proposed {
t.Fatalf("a head is %s", w.state(id))
}
w.h.Checked(verdict("novox/app", 7, head, "pass"))
d := w.delivery(id)
if d.State != Ready {
t.Fatalf("a passing verdict left it %s", d.State)
}
var path []State
for _, tr := range d.Transitions {
path = append(path, tr.To)
}
if strings.Join(statesOf(path), ",") != "proposed,ready" {
t.Fatalf("its transitions are %v", path)
}
w.settleAll()
notes := w.forge.notesOn(head)
if len(notes) != 2 || !strings.Contains(notes[1], "proposed -> ready") {
t.Fatalf("the head's note is %q", notes)
}
if !strings.Contains(w.forge.statuses["novox/app@"+head+" mesh/delivery"], "success ready") {
t.Fatalf("the head's status is %q", w.forge.statuses)
}
if v := w.forge.views["novox/app#7"]; !strings.Contains(v, viewMarker) || !strings.Contains(v, "**ready**") {
t.Fatalf("the pull request's view is %q", v)
}
// A failing verdict for another head rejects it, and a newer head closes the older.
w.h.PullUpdated(pr("novox/app", 7, "bbbbbbbbbbbb2222", "feat/x"))
if w.state(id) != Closed {
t.Fatalf("a newer head left the older %s", w.state(id))
}
w.h.Checked(verdict("novox/app", 7, "bbbbbbbbbbbb2222", "fail"))
if s := w.state(IDOf("novox/app", "bbbbbbbbbbbb2222")); s != Rejected {
t.Fatalf("a failing verdict left it %s", s)
}
}
func statesOf(ss []State) []string {
var out []string
for _, s := range ss {
out = append(out, string(s))
}
return out
}
// A merge puts the delivery on the trunk and ends it there: merged, with the merge commit's note and the pull request's
// status naming the source revision it moved. Nothing after the merge is derived (ADR 0539 §17).
func TestAMergeIsMergedAndTheGateDerivesNothingAfterIt(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.h.Checked(verdict("novox/app", 7, head, "pass"))
w.h.PullMerged(merged("novox/app", 7, head, merge))
d := w.delivery(id)
if d.State != Merged || d.MergedAs != merge {
t.Fatalf("a merge left it %s as %q", d.State, d.MergedAs)
}
w.settleAll()
w.later(72 * time.Hour)
w.settleAll()
if s := w.state(id); s != Merged {
t.Fatalf("time moved a merged delivery to %s", s)
}
if st := w.forge.statuses["novox/app@"+head+" mesh/delivery"]; !strings.HasPrefix(st, "success merged: source.main moved to novox/app@"+merge[:12]) {
t.Fatalf("the pull request's mesh/delivery status is %q", st)
}
view := w.forge.views["novox/app#7"]
if !strings.Contains(view, "**Source**") || !strings.Contains(view, "source.main moved to novox/app@"+merge[:12]) ||
!strings.Contains(view, "mesh-delivery-view") || strings.Contains(view, "**Delivery plan**") || strings.Contains(view, "**Machines**") {
t.Fatalf("the pull request's view is %q", view)
}
n := 0
for _, s := range w.said {
if s["id"] == id {
n++
}
}
if n != len(d.Transitions) {
t.Fatalf("%d transitions, %d said", len(d.Transitions), n)
}
}
// The trunk rule: a ready delivery whose commit never reached the trunk is never merged.
func TestACommitOffTheTrunkIsNeverMerged(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.h.Checked(verdict("novox/app", 7, head, "pass"))
w.settleAll()
if s := w.state(id); s != Ready {
t.Fatalf("an off-trunk head is %s", s)
}
}
// A restart resumes: a new holder over the same state stands every delivery where it was.
func TestARestartResumesFromTheState(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.h.Checked(verdict("novox/app", 7, head, "pass"))
w.h.PullMerged(merged("novox/app", 7, head, merge))
w.settleAll()
if s := w.state(id); s != Merged {
t.Fatalf("before the restart it is %s", s)
}
again := newWorldOver(t, w.store, w.forge, w.now.Add(time.Hour))
if s := again.state(id); s != Merged {
t.Fatalf("read back, it is %s", s)
}
}
// A delivery kept in a state an earlier owner had and this one does not is read as the state it came to and kept so.
func TestADeliveryKeptInAGoneStateIsReadAsTheMergeStateItCameTo(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.h.Checked(verdict("novox/app", 7, head, "pass"))
w.h.PullMerged(merged("novox/app", 7, head, merge))
w.settleAll()
raw := w.store.deliveries[id]
w.store.deliveries[id] = []byte(strings.Replace(string(raw), `"state":"merged"`, `"state":"delivering"`, 1))
again := newWorldOver(t, w.store, w.forge, w.now.Add(time.Hour))
if s := again.state(id); s != Merged {
t.Fatalf("a delivering delivery was read as %s", s)
}
again.settleAll()
if !strings.Contains(string(w.store.deliveries[id]), `"state":"merged"`) {
t.Fatalf("the read state was not kept: %s", w.store.deliveries[id])
}
}
// Kept before it is said: with the state unreachable, nothing is emitted or noted; once it is kept, all of it.
func TestATransitionIsKeptBeforeItIsSaid(t *testing.T) {
w := newWorld(t)
w.store.failing = true
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.settleAll()
if len(w.said) != 0 || len(w.forge.notesOn(head)) != 0 {
t.Fatalf("said before it was kept: %v %v", w.said, w.forge.notesOn(head))
}
w.store.failing = false
w.settleAll()
if len(w.said) != 1 || len(w.forge.notesOn(head)) != 1 {
t.Fatalf("once kept, said %d and noted %d", len(w.said), len(w.forge.notesOn(head)))
}
// A bucket the bus cannot take is written again; a note is appended once however often it is asked.
w.owed.down = true
w.h.Checked(verdict("novox/app", 7, head, "pass"))
w.settleAll()
if len(w.forge.notesOn(head)) != 1 {
t.Fatal("noted while the bus was away")
}
w.owed.down = false
w.later(time.Hour) // each failure put its next try later: past every one of them
w.settleAll()
w.settleAll()
if notes := w.forge.notesOn(head); len(notes) != 2 {
t.Fatalf("after the bus came back the note has %d line(s): %q", len(notes), notes)
}
if d := w.delivery(IDOf("novox/app", head)); len(d.Owed) != 0 {
t.Fatalf("still owed: %+v", d.Owed)
}
}
// A merge before the check passed is held for a person; a merge this owner never heard the head of is made
// from the forge's word and merged when the forge says its gate passed.
func TestAMergeWithoutAPassingCheckIsHeld(t *testing.T) {
w := newWorld(t)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
w.h.Checked(verdict("novox/app", 7, head, "fail"))
w.h.PullMerged(merged("novox/app", 7, head, merge))
if s := w.state(IDOf("novox/app", head)); s != Held {
t.Fatalf("a rejected head merged is %s", s)
}
// Never heard: the forge's statuses on its head decide.
m := merged("novox/lab", 3, "eeeeeeeeeeee5555", "ffffffffffff6666")
m.HeadChecks = map[string]string{"mesh/check": "success"}
m.Paths = []string{"README.md"}
w.h.PullMerged(m)
w.settleAll()
d := w.delivery(IDOf("novox/lab", "eeeeeeeeeeee5555"))
if d.State != Merged {
t.Fatalf("a merge whose head passed by the forge's word is %s", d.State)
}
}
// A group merged whole ends merged, member by member and as a group, and source.main holds its heads (ADR 0539 §17, R6-C2).
func TestAMergedGroupEndsMergedWithSourceMainMoved(t *testing.T) {
w := newWorld(t)
heads := map[string]string{"novox/a": "a1a1a1a1a1a1a1", "novox/b": "b2b2b2b2b2b2b2"}
ids := map[string]string{}
for i, repo := range []string{"novox/a", "novox/b"} {
ids[repo] = IDOf(repo, heads[repo])
w.h.PullUpdated(pr(repo, i+1, heads[repo], "feat/y", "x"))
w.h.Checked(verdict(repo, i+1, heads[repo], "pass"))
}
w.settleAll()
w.h.Checked(CheckedEvent{Group: "feat/y", ID: "check-1", Verdict: "pass", Summary: "compose"})
for i, repo := range []string{"novox/a", "novox/b"} {
w.h.PullMerged(merged(repo, i+1, heads[repo], strings.Repeat(string(rune('p'+i)), 12)))
}
w.settleAll()
for repo, id := range ids {
if s := w.state(id); s != Merged {
t.Errorf("%s is %s, wanted merged", repo, s)
}
}
g := w.h.groups["feat/y"]
if state, why := GroupState(g, w.h.membersOf(g)); state != "merged" {
t.Fatalf("the group is %s (%s), wanted merged", state, why)
}
if w.source.main == nil || w.source.main.Heads["novox/a"] != strings.Repeat("p", 12) || w.source.main.Heads["novox/b"] != strings.Repeat("q", 12) {
t.Fatalf("source.main is %+v, wanted both merges", w.source.main)
}
}
// ADR 0419 §2, issue 978: a description's `after:` line is not read, and says so once on its pull request. The
// composed check is asked as for any other group: no order is asked of the controller first, and none gates it.
func TestAnAfterLineIsNotReadAndSaidOnceAndNothingWaitsForAnOrder(t *testing.T) {
w := newWorld(t)
w.op.auto = false
pa := pr("novox/a", 1, "a1a1a1a1a1a1a1", "feat/z")
pa.Body = "Goes after the other.\n\nafter: novox/b\n"
w.h.PullUpdated(pa)
w.h.PullUpdated(pr("novox/b", 2, "b2b2b2b2b2b2b2", "feat/z"))
w.h.Checked(verdict("novox/a", 1, "a1a1a1a1a1a1a1", "pass"))
w.h.Checked(verdict("novox/b", 2, "b2b2b2b2b2b2b2", "pass"))
w.settleAll()
w.later(time.Minute)
w.settleAll()
said := 0
for _, c := range w.forge.comments {
if strings.Contains(c, "not read") && strings.Contains(c, "novox/a#1") {
said++
}
}
if said != 1 {
t.Fatalf("the after: line is not said once on its pull request: %v", w.forge.comments)
}
// A new head of the same pull request, with the line still there, is not told again.
pa2 := pr("novox/a", 1, "a9a9a9a9a9a9a9", "feat/z")
pa2.Body = pa.Body
w.h.PullUpdated(pa2)
w.settleAll()
said = 0
for _, c := range w.forge.comments {
if strings.Contains(c, "not read") {
said++
}
}
if said != 1 {
t.Fatalf("said again for a new head: %v", w.forge.comments)
}
}
// Stalled: a delivery past its state's bound is listed with what H2 may do; close takes only that. Only a proposed
// delivery has a bound now: its check was asked and not answered.
func TestStalledAndCloseWorkFromTheTable(t *testing.T) {
w := newWorld(t)
id := IDOf("novox/app", head)
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
if _, err := w.h.Close(id, "too early"); err == nil {
t.Fatal("closed within its bound")
}
w.later(3 * time.Hour)
stalled := w.h.Stalled()
if len(stalled) != 1 || stalled[0].ID != id || stalled[0].State != Proposed {
t.Fatalf("stalled %+v", stalled)
}
// A merge is the gate's last word: it is never stalled, however old.
w.h.Checked(verdict("novox/app", 7, head, "pass"))
w.h.PullMerged(merged("novox/app", 7, head, merge))
w.later(72 * time.Hour)
for _, s := range w.h.Stalled() {
if s.ID == id {
t.Fatalf("a merged delivery is stalled: %+v", s)
}
}
if _, err := w.h.Close(id, "x"); err == nil {
t.Fatal("H2 closed a merged delivery")
}
// A merge without a passing check is held: a person's, with no transition for H2.
w.h.PullUpdated(pr("novox/app", 8, "ffffffffffff0000", "feat/q"))
w.h.PullMerged(merged("novox/app", 8, "ffffffffffff0000", "eeeeeeeeeeee0000"))
w.later(48 * time.Hour)
if _, err := w.h.Close(IDOf("novox/app", "ffffffffffff0000"), "x"); err == nil {
t.Fatal("H2 closed a held delivery")
}
}
// novox/hq issue 867, ADR 0382 §1 (contract A): each member of a group in `groups` also says its pull
// request number, full head, base branch and whether it is open; `members` keeps its text form.
func TestGroupsNamesEachMembersPullRequestHeadBaseAndOpen(t *testing.T) {
w := newWorld(t)
headA := "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"
headB := "b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2"
pb := pr("novox/b", 2, headB, "feat/z")
pb.Base = "release/1"
w.h.PullUpdated(pr("novox/a", 1, headA, "feat/z"))
w.h.PullUpdated(pb)
lines := w.h.Groups(false)
if len(lines) != 1 || len(lines[0].Details) != 2 || len(lines[0].Members) != 2 {
t.Fatalf("groups: %+v", lines)
}
got := map[string]MemberLine{}
for _, m := range lines[0].Details {
got[m.Repository] = m
}
a, b := got["novox/a"], got["novox/b"]
if a.Number != 1 || a.Head != headA || a.Base != "main" || !a.Open || a.ID == "" {
t.Fatalf("member a: %+v", a)
}
if b.Number != 2 || b.Head != headB || b.Base != "release/1" || !b.Open {
t.Fatalf("member b: %+v", b)
}
if !strings.Contains(lines[0].Members[0], "(") {
t.Fatalf("members lost its text form: %v", lines[0].Members)
}
// Closed unmerged: still listed while the group is, but not open.
w.h.PullClosed(pr("novox/b", 2, headB, "feat/z"))
for _, l := range w.h.Groups(true) {
for _, m := range l.Details {
if m.Repository == "novox/b" && m.Open {
t.Fatalf("a closed pull request is open: %+v", m)
}
}
}
// JSON keys are the contract's.
raw, _ := json.Marshal(a)
for _, k := range []string{`"number":1`, `"head":"` + headA + `"`, `"base":"main"`, `"open":true`} {
if !strings.Contains(string(raw), k) {
t.Fatalf("%s lacks %s", raw, k)
}
}
}