mesh/merge-gate pass: builds gitea, mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer delivery to the same trunk took over its walk
mesh/delivery-group group feat/delivery-checks-verb delivered: every member is delivered
Reading a pull request's mesh/merge-gate and mesh/repo-check meant scraping the controller's journal: the forge clips each status to 140 characters and nothing returned the verdict whole. mesh-delivery already keeps the verdict; it now keeps the machine that ran it, the layers' modules and the report, and its checks verb joins that to the forge's statuses, read through a new gitea_commit_statuses tool that also says whether the base's protection lets the commit merge. after: novox/mesh-controller
243 lines
10 KiB
Go
243 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The `checks` verb (novox/hq ADR 0239): what the mesh's checks said of a pull request's head or a commit —
|
|
// the forge's statuses, the verdict whole as the controller said it, and whether the base's protection lets
|
|
// it merge.
|
|
|
|
// checkedAs is the controller's `checked` as it comes off the bus, both layers said.
|
|
func checkedAs(t *testing.T, repo string, number int, sha, gate, gateSaid, repoVerdict, repoSaid string) CheckedEvent {
|
|
t.Helper()
|
|
owner, name, _ := strings.Cut(repo, "/")
|
|
raw := map[string]any{"owner": owner, "repo": name, "number": number, "commit": sha, "verdict": gate,
|
|
"summary": gateSaid, "id": "build-" + sha[:8], "on": "the-build-node", "report": "== compose\n" + gateSaid,
|
|
"gate": map[string]any{"verdict": gate, "summary": gateSaid, "modules": []string{"app"}, "dependents": []string{"web"}},
|
|
"repo-check": map[string]any{"verdict": repoVerdict, "summary": repoSaid}}
|
|
var c CheckedEvent
|
|
if err := json.Unmarshal(mustJSON(raw), &c); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// statusesAsTheForgeSets puts the two statuses the forge's holder sets from a verdict: clipped, as it keeps them.
|
|
func statusesAsTheForgeSets(w *world, repo, sha string, c CheckedEvent) {
|
|
owner, name, _ := strings.Cut(repo, "/")
|
|
_ = w.forge.Status(owner, name, sha, ctxGate, forgeStateOf(c.Gate.Verdict), clip(c.Gate.Verdict+": "+c.Gate.Summary, 40), "https://forge.invalid/pr")
|
|
_ = w.forge.Status(owner, name, sha, ctxRepo, forgeStateOf(c.RepoCheck.Verdict), clip(c.RepoCheck.Verdict+": "+c.RepoCheck.Summary, 40), "https://forge.invalid/pr")
|
|
}
|
|
|
|
func statusNamed(a *ChecksAnswer, name string) *CheckStatus {
|
|
for i := range a.Statuses {
|
|
if a.Statuses[i].Name == name {
|
|
return &a.Statuses[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestChecksOfAPullRequestSayEveryStatusTheVerdictWholeAndWhetherItMerges(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.forge.required = []string{ctxGate, ctxRepo}
|
|
w.forge.pulls["novox/app#7"] = ForgePull{Number: 7, Title: "a change", State: "open", Base: "main", Head: "feat/x", HeadSHA: head}
|
|
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x", "src/a.go"))
|
|
said := "0 of 4 compose: anchor refuses app's manifest — a provision nothing provides, and three more machines alike"
|
|
c := checkedAs(t, "novox/app", 7, head, "fail", said, "pass", "its merge-check.sh passed")
|
|
w.h.Checked(c)
|
|
w.settleAll()
|
|
statusesAsTheForgeSets(w, "novox/app", head, c)
|
|
|
|
a, err := w.h.Checks("novox/app", 7, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if a.Commit != head || a.Pull == nil || a.Pull.Number != 7 || a.Forge != "" {
|
|
t.Fatalf("the head read is %q, pull %+v, forge %q", a.Commit, a.Pull, a.Forge)
|
|
}
|
|
gate := statusNamed(a, ctxGate)
|
|
if gate == nil || gate.State != "failure" || gate.Source != "the forge" || gate.SetBy != "mesh-admin" {
|
|
t.Fatalf("the gate's status is %+v", gate)
|
|
}
|
|
// The forge clipped it; the verdict says it whole, with the machine, the build and the modules.
|
|
if strings.Contains(gate.Description, "three more machines") {
|
|
t.Fatalf("the fake forge did not clip: %q", gate.Description)
|
|
}
|
|
if !strings.Contains(gate.Verdict, "FAIL — 0 of 4 compose") || !strings.Contains(gate.Verdict, "three more machines") ||
|
|
!strings.Contains(gate.Verdict, "modules: app; built after them: web") {
|
|
t.Fatalf("the gate's verdict is %q", gate.Verdict)
|
|
}
|
|
if gate.Build != "build-"+head[:8] || gate.CheckedOn != "the-build-node" || gate.CheckedAt == "" {
|
|
t.Fatalf("the gate was checked as %q on %q at %q", gate.Build, gate.CheckedOn, gate.CheckedAt)
|
|
}
|
|
repo := statusNamed(a, ctxRepo)
|
|
if repo == nil || repo.State != "success" || repo.Verdict != "PASS — its merge-check.sh passed" || repo.CheckedOn != "the-build-node" {
|
|
t.Fatalf("the repository check's status is %+v", repo)
|
|
}
|
|
if d := statusNamed(a, ctxDelivery); d == nil || !strings.HasPrefix(d.Verdict, "rejected: waits for a new head") {
|
|
t.Fatalf("the delivery's status is %+v", d)
|
|
}
|
|
if a.Verdict == nil || !strings.Contains(a.Verdict.Report, "== compose") {
|
|
t.Fatalf("the verdict whole is %+v", a.Verdict)
|
|
}
|
|
if a.Delivery == nil || a.Delivery.ID != IDOf("novox/app", head) || a.Delivery.State != Rejected {
|
|
t.Fatalf("the delivery is %+v", a.Delivery)
|
|
}
|
|
if a.Merge.Mergeable == nil || *a.Merge.Mergeable || len(a.Merge.Blocking) != 1 || a.Merge.Blocking[0].Context != ctxGate {
|
|
t.Fatalf("the merge is %+v", a.Merge)
|
|
}
|
|
if !strings.Contains(a.Says, "novox/app#7 at aaaaaaaa") || !strings.Contains(a.Says, "NOT mergeable") {
|
|
t.Fatalf("it says %q", a.Says)
|
|
}
|
|
// The forge was asked for the pull request, with the base this owner knew.
|
|
if len(w.forge.asked) != 1 || w.forge.asked[0] != "novox/app#7@ base=main" {
|
|
t.Fatalf("the forge was asked %v", w.forge.asked)
|
|
}
|
|
}
|
|
|
|
func TestANewHeadIsReadWithItsOwnVerdictNeverTheOlderOnes(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.forge.required = []string{ctxGate, ctxRepo}
|
|
newer := "bbbbbbbbbbbb2222"
|
|
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x"))
|
|
w.h.Checked(checkedAs(t, "novox/app", 7, head, "fail", "0 of 4 compose", "pass", "passed"))
|
|
w.h.PullUpdated(pr("novox/app", 7, newer, "feat/x"))
|
|
w.forge.pulls["novox/app#7"] = ForgePull{Number: 7, State: "open", Base: "main", HeadSHA: newer}
|
|
|
|
a, err := w.h.Checks("novox/app", 7, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if a.Commit != newer || a.Delivery == nil || a.Delivery.ID != IDOf("novox/app", newer) || a.Delivery.State != Proposed {
|
|
t.Fatalf("the new head reads as %q, %+v", a.Commit, a.Delivery)
|
|
}
|
|
if a.Verdict != nil {
|
|
t.Fatalf("the older head's verdict was given for the newer: %+v", a.Verdict)
|
|
}
|
|
if a.Merge.Mergeable == nil || *a.Merge.Mergeable || len(a.Merge.Blocking) != 2 || a.Merge.Blocking[0].State != "missing" {
|
|
t.Fatalf("a head not checked yet merges as %+v", a.Merge)
|
|
}
|
|
|
|
// Passed, it merges.
|
|
c := checkedAs(t, "novox/app", 7, newer, "pass", "every machine composes", "pass", "its merge-check.sh passed")
|
|
w.h.Checked(c)
|
|
statusesAsTheForgeSets(w, "novox/app", newer, c)
|
|
a, _ = w.h.Checks("novox/app", 7, "")
|
|
if a.Merge.Mergeable == nil || !*a.Merge.Mergeable || !strings.Contains(a.Says, "mergeable") || strings.Contains(a.Says, "NOT") {
|
|
t.Fatalf("a head that passed: %+v, says %q", a.Merge, a.Says)
|
|
}
|
|
if g := statusNamed(a, ctxGate); g == nil || g.Verdict != "PASS — every machine composes (modules: app; built after them: web)" {
|
|
t.Fatalf("its gate: %+v", g)
|
|
}
|
|
}
|
|
|
|
func TestACommitIsReadByTheStartOfItsShaAndAMergeByItsDelivery(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x"))
|
|
c := checkedAs(t, "novox/app", 7, head, "pass", "every machine composes", "pass", "passed")
|
|
w.h.Checked(c)
|
|
statusesAsTheForgeSets(w, "novox/app", head, c)
|
|
|
|
a, err := w.h.Checks("Novox/App.git", 0, head[:8])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if a.Commit != head || a.Verdict == nil || a.Delivery == nil || a.Pull != nil {
|
|
t.Fatalf("by the start of its sha: %q, %+v, %+v", a.Commit, a.Verdict, a.Delivery)
|
|
}
|
|
// No protection: nothing is required, so it merges.
|
|
if a.Merge.Mergeable == nil || !*a.Merge.Mergeable {
|
|
t.Fatalf("with no protection: %+v", a.Merge)
|
|
}
|
|
|
|
mergedAs := "dddddddddddd9999"
|
|
w.h.PullMerged(merged("novox/app", 7, head, mergedAs))
|
|
_ = w.forge.Status("novox", "app", mergedAs, ctxDelivery, "pending", "published", "")
|
|
a, err = w.h.Checks("novox/app", 0, mergedAs)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if a.Delivery == nil || a.Delivery.ID != IDOf("novox/app", head) {
|
|
t.Fatalf("the merge commit's delivery is %+v", a.Delivery)
|
|
}
|
|
if a.Verdict != nil {
|
|
t.Fatalf("the head's verdict was given for the merge commit: %+v", a.Verdict)
|
|
}
|
|
if s := statusNamed(a, ctxDelivery); s == nil || s.State != "pending" || s.Verdict == "" {
|
|
t.Fatalf("the merge's delivery status: %+v", s)
|
|
}
|
|
}
|
|
|
|
func TestWithTheForgeAwayTheVerdictIsSaidAndWhetherItMergesIsNotKnown(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.h.PullUpdated(pr("novox/app", 7, head, "feat/x"))
|
|
w.h.Checked(checkedAs(t, "novox/app", 7, head, "warning", "every machine composes, with a note", "error", "the toolchain was not there"))
|
|
w.forge.down = true
|
|
|
|
a, err := w.h.Checks("novox/app", 7, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if a.Forge == "" || a.Commit != head || a.Verdict == nil {
|
|
t.Fatalf("with the forge away: forge %q, commit %q, verdict %+v", a.Forge, a.Commit, a.Verdict)
|
|
}
|
|
gate, repo := statusNamed(a, ctxGate), statusNamed(a, ctxRepo)
|
|
if gate == nil || gate.State != "success" || !strings.Contains(gate.Source, "could not be read") ||
|
|
repo == nil || repo.State != "error" || repo.Verdict != "ERROR — the toolchain was not there" {
|
|
t.Fatalf("the statuses from the verdict: %+v %+v", gate, repo)
|
|
}
|
|
if a.Merge.Mergeable != nil || !strings.Contains(a.Says, "mergeable: not known") {
|
|
t.Fatalf("whether it merges, with the forge away: %+v, %q", a.Merge, a.Says)
|
|
}
|
|
|
|
// The protection unreadable: the statuses are said, and whether it merges is not known.
|
|
w.forge.down, w.forge.protectionDown = false, true
|
|
w.forge.pulls["novox/app#7"] = ForgePull{Number: 7, Base: "main", HeadSHA: head}
|
|
a, _ = w.h.Checks("novox/app", 7, "")
|
|
if a.Merge.Mergeable != nil || a.Forge != "" {
|
|
t.Fatalf("with the protection unreadable: %+v", a.Merge)
|
|
}
|
|
}
|
|
|
|
func TestChecksRefuseWhatNamesNoCommit(t *testing.T) {
|
|
w := newWorld(t)
|
|
for _, c := range []struct {
|
|
repo string
|
|
number int
|
|
commit string
|
|
says string
|
|
}{
|
|
{"novox/app", 0, "", "number or a commit"},
|
|
{"app", 7, "", "owner/repository"},
|
|
{"novox/app/x", 7, "", "owner/repository"},
|
|
{"novox/app", 0, "abc", "not a commit"},
|
|
{"novox/app", 0, "zzzzzzzzzz", "not a commit"},
|
|
{"novox/app", -1, "", "not a pull request's number"},
|
|
} {
|
|
if _, err := w.h.Checks(c.repo, c.number, c.commit); err == nil || !strings.Contains(err.Error(), c.says) {
|
|
t.Errorf("%s #%d %q: %v", c.repo, c.number, c.commit, err)
|
|
}
|
|
}
|
|
if len(w.forge.asked) != 0 {
|
|
t.Fatalf("the forge was asked for what names nothing: %v", w.forge.asked)
|
|
}
|
|
}
|
|
|
|
func TestANumberIsTakenAsANumberOrAsText(t *testing.T) {
|
|
for _, c := range []struct {
|
|
in any
|
|
want int
|
|
ok bool
|
|
}{{float64(168), 168, true}, {"168", 168, true}, {"#168", 168, true}, {nil, 0, true}, {"", 0, true},
|
|
{"x", 0, false}, {1.5, 0, false}} {
|
|
got, err := intArg(map[string]any{"number": c.in}, "number")
|
|
if (err == nil) != c.ok || got != c.want {
|
|
t.Errorf("%v → %d %v", c.in, got, err)
|
|
}
|
|
}
|
|
}
|