mesh-config raises config-changed (hold, drop) naming the pull request; the delivery, or its group's composed check, is asked again on the gate's own path, with why in the transition. Not the recheck verb.
146 lines
5.8 KiB
Go
146 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
// Issue 1826 (novox/hq ADR 0502 clause 5): when mesh-config holds or drops a value for a pull request that is a
|
|
// delivery, its check, or its group's composed check, is asked again, and the transition says why.
|
|
|
|
func configChanged(action, pull string) ConfigChange {
|
|
c := ConfigChange{Action: action, Pull: pull, Head: head, Key: "media-gid"}
|
|
c.Level.Node, c.Level.Module = "ace", "n8n"
|
|
return c
|
|
}
|
|
|
|
// 1826/K4: a lone delivery that was ready is proposed again with its merge check pending, its check asked, and the
|
|
// transition says that a config value was held.
|
|
func TestAHeldValueAsksTheReadyDeliverysCheckAgain(t *testing.T) {
|
|
w := newWorld(t)
|
|
id := heldGreen(t, w, "novox/app", 7)
|
|
asked := len(w.ctl.checks)
|
|
w.h.ConfigChanged(configChanged("hold", "novox/app#7"))
|
|
if got := w.ctl.checks[asked:]; len(got) != 1 || got[0] != ":"+id {
|
|
t.Fatalf("asked %v; wanted its own check once", got)
|
|
}
|
|
d := w.delivery(id)
|
|
last := d.Transitions[len(d.Transitions)-1]
|
|
if d.State != Proposed || last.Event != EvRecheck || !strings.Contains(last.Why, "media-gid") ||
|
|
!strings.Contains(last.Why, "held") || last.By != byOwner {
|
|
t.Errorf("state %s, last transition %+v", d.State, last)
|
|
}
|
|
if s := w.forge.statuses["novox/app@"+head+" "+ctxGate]; !strings.HasPrefix(s, "pending checking again") {
|
|
t.Errorf("the merge check says %q: the old verdict would still let it merge", s)
|
|
}
|
|
}
|
|
|
|
// 1826/K4: a rejected delivery (the very case: the composed check failed on the missing value) is asked again on a
|
|
// hold; and a drop asks it again too.
|
|
func TestAHeldValueAsksARejectedDeliverysCheckAgainAndADropToo(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.h.PullUpdated(pr("novox/app", 8, head, "feat/y", "src/a.go"))
|
|
w.h.Checked(verdict("novox/app", 8, head, "fail"))
|
|
w.settleAll()
|
|
id := IDOf("novox/app", head)
|
|
if w.state(id) != Rejected {
|
|
t.Fatalf("it is %s", w.state(id))
|
|
}
|
|
asked := len(w.ctl.checks)
|
|
w.h.ConfigChanged(configChanged("hold", "novox/app#8"))
|
|
if w.state(id) != Proposed || len(w.ctl.checks) != asked+1 {
|
|
t.Fatalf("a hold left it %s with %v asked", w.state(id), w.ctl.checks[asked:])
|
|
}
|
|
w.h.Checked(verdict("novox/app", 8, head, "pass"))
|
|
w.settleAll()
|
|
asked = len(w.ctl.checks)
|
|
w.h.ConfigChanged(configChanged("drop", "novox/app#8"))
|
|
d := w.delivery(id)
|
|
if len(w.ctl.checks) != asked+1 || !strings.Contains(d.Transitions[len(d.Transitions)-1].Why, "dropped") {
|
|
t.Errorf("a drop asked %v; last transition %+v", w.ctl.checks[asked:], d.Transitions[len(d.Transitions)-1])
|
|
}
|
|
}
|
|
|
|
// 1826/K4: a delivery whose check is on its way is asked again, its old ask replaced, and says why in its note.
|
|
func TestAHeldValueAsksTheCheckOfAProposedDeliveryAgain(t *testing.T) {
|
|
w := newWorld(t)
|
|
w.h.PullUpdated(pr("novox/app", 9, head, "feat/z", "src/a.go"))
|
|
w.settleAll()
|
|
id := IDOf("novox/app", head)
|
|
before := w.delivery(id).CheckAsk
|
|
asked := len(w.ctl.checks)
|
|
w.h.ConfigChanged(configChanged("hold", "novox/app#9"))
|
|
d := w.delivery(id)
|
|
if len(w.ctl.checks) != asked+1 || d.CheckAsk == nil || (before != nil && d.CheckAsk.ID == before.ID) ||
|
|
!strings.Contains(d.CheckAsk.Why, "held") {
|
|
t.Errorf("asked %v; ask %+v (was %+v)", w.ctl.checks[asked:], d.CheckAsk, before)
|
|
}
|
|
if d.State != Proposed {
|
|
t.Errorf("it is %s", d.State)
|
|
}
|
|
}
|
|
|
|
// 1826/K4: a hold for a member of a group asks the member's check again and then the group's composed check, which
|
|
// the group waits for.
|
|
func TestAHeldValueForAGroupMemberAsksTheComposedCheckAgain(t *testing.T) {
|
|
w := approvedGroup(t)
|
|
g := w.h.groups["feat/g"]
|
|
firstComposed := g.Check.Asked
|
|
asked := len(w.ctl.checks)
|
|
w.h.ConfigChanged(configChanged("hold", gCat+"#2"))
|
|
if got := w.ctl.checks[asked:]; len(got) != 1 || got[0] != ":"+IDOf(gCat, gHk) {
|
|
t.Fatalf("a hold asked %v; wanted the member's own check", got)
|
|
}
|
|
if g = w.h.groups["feat/g"]; g.Check.OutOfDate == "" || !strings.Contains(g.Check.OutOfDate, "rechecked") {
|
|
t.Fatalf("the composed check stands: %+v", g.Check)
|
|
}
|
|
w.later(60e9)
|
|
w.h.Checked(judged(gCat, 2, gHk, "pass", gTrunk0))
|
|
w.settleAll()
|
|
last := w.ctl.checks[len(w.ctl.checks)-1]
|
|
if !strings.HasPrefix(last, "feat/g:") || w.h.groups["feat/g"].Check.Asked == firstComposed {
|
|
t.Errorf("the composed check was not asked again: %v", w.ctl.checks[asked:])
|
|
}
|
|
}
|
|
|
|
// 1826/K4: what is not a hold or a drop for a delivery of this owner asks nothing: the merge's set, another action,
|
|
// another pull request, a pull request with no delivery, and a delivery already merged.
|
|
func TestOnlyAHoldOrADropForADeliveryAsksACheckAgain(t *testing.T) {
|
|
w := newWorld(t)
|
|
heldGreen(t, w, "novox/app", 7)
|
|
asked := len(w.ctl.checks)
|
|
for _, c := range []ConfigChange{configChanged("set", "novox/app#7"), configChanged("resolve", ""),
|
|
configChanged("hold", "novox/app#99"), configChanged("hold", "novox/other#7"), configChanged("hold", ""),
|
|
configChanged("hold", "garbage")} {
|
|
w.h.ConfigChanged(c)
|
|
}
|
|
if len(w.ctl.checks) != asked || w.state(IDOf("novox/app", head)) != Ready {
|
|
t.Errorf("asked %v; it is %s", w.ctl.checks[asked:], w.state(IDOf("novox/app", head)))
|
|
}
|
|
}
|
|
|
|
// 1826/K4: the event is heard as mesh-config's config-changed, and it is no recheck verb.
|
|
func TestTheConfigChangedEventIsFollowed(t *testing.T) {
|
|
w := newWorld(t)
|
|
heldGreen(t, w, "novox/app", 7)
|
|
found := false
|
|
for _, f := range followed {
|
|
found = found || f == "mesh-config.config-changed"
|
|
}
|
|
if !found {
|
|
t.Fatalf("not followed: %v", followed)
|
|
}
|
|
raw, _ := json.Marshal(map[string]any{"action": "hold", "pull": "novox/app#7", "head": head, "key": "media-gid",
|
|
"level": map[string]any{"level": "module", "node": "ace", "module": "n8n"}})
|
|
asked := len(w.ctl.checks)
|
|
if err := handle(w.h, stdio.Envelope{Key: "mesh-config.config-changed", Body: raw}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(w.ctl.checks) != asked+1 {
|
|
t.Errorf("the event asked %v", w.ctl.checks[asked:])
|
|
}
|
|
}
|