mesh/delivery closed: the pull request closed unmerged
mesh/check pass 96a37f58ca76 on 030f9ee4e780 | unit 1173 run, 3272 kept, 13 skipped | integration 1212 run, 3274 kept, 22 skipped | over bound: unit, …
The controller's declared-config mirror never held de-spiegel's keys, and Resolve skipped an assigned module it held no declarations of, silently: de-spiegel stayed held on novox with its values set. Declarations now come from the indexer's trunk manifest with the wanted build's laid over it per node; which module runs where from the seat mesh-placement alone. A placed module with no readable manifest is listed as unresolved with why, logged, and keeps its last resolved values.
496 lines
21 KiB
Go
496 lines
21 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Issue 1826 (novox/hq ADR 0502): a value held for the open pull request that introduces its key.
|
|
|
|
const thePull = "novox/mesh-catalog#434"
|
|
|
|
// n8nPullDeclares is what the controller tells of the pull request: n8n gets a required number media-gid.
|
|
func n8nPullDeclares() map[string]any {
|
|
return map[string]any{"n8n": map[string]any{"media-gid": map[string]any{"type": "number",
|
|
"description": "the media group", "required": true, "minimum": 1, "maximum": 65535}}}
|
|
}
|
|
|
|
// aHoldingMesh is a proposing mesh with n8n assigned to ace and declaring nothing on the trunk (the case of the
|
|
// issue), and a stand-in forge.
|
|
func aHoldingMesh(t *testing.T) (*Config, *memStore, *standIn, *standForge) {
|
|
t.Helper()
|
|
c, m, r := aProposingMesh(t)
|
|
m.declare(t, "n8n", `{"module":"n8n","settings":{}}`)
|
|
m.assign(t, "ace", "n8n")
|
|
f := &standForge{state: map[string][2]bool{}}
|
|
c.Forge = f
|
|
if _, err := c.Reconcile(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m.events, m.handActs = nil, nil
|
|
return c, m, r, f
|
|
}
|
|
|
|
type standForge struct {
|
|
state map[string][2]bool // pull -> open, merged
|
|
head string
|
|
err error
|
|
}
|
|
|
|
func (f *standForge) Pull(owner, repo string, n int) (bool, bool, string, error) {
|
|
if f.err != nil {
|
|
return false, false, "", f.err
|
|
}
|
|
st, ok := f.state[(PullEvent{Owner: owner, Repo: repo, Number: n}).name()]
|
|
if !ok {
|
|
return true, false, f.head, nil
|
|
}
|
|
return st[0], st[1], f.head, nil
|
|
}
|
|
|
|
func tell(t *testing.T, c *Config, pull, head string, declares map[string]any) *PullDeclaresAnswer {
|
|
t.Helper()
|
|
a, err := c.Do(Call{Action: "pull-declares", Pull: pull, Head: head, Declares: declares, Caller: ControllerCaller})
|
|
if err != nil {
|
|
t.Fatalf("pull-declares: %v", err)
|
|
}
|
|
return a.(*PullDeclaresAnswer)
|
|
}
|
|
|
|
// holdIt proposes media-gid=2000 for n8n on ace held for the pull request and has the operator approve it.
|
|
func holdIt(t *testing.T, c *Config, m *memStore, r *standIn) string {
|
|
t.Helper()
|
|
a := propose(t, c, Call{Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull,
|
|
Why: "n8n's container joins the media group, held for #434"})
|
|
r.choose(t, a.Proposal, OptionApprove, nil)
|
|
said, err := c.Decide()
|
|
if err != nil || len(said) != 1 || !strings.Contains(said[0], "approved by the operator and held") {
|
|
t.Fatalf("the Approve decided %v (%v)", said, err)
|
|
}
|
|
return a.Proposal
|
|
}
|
|
|
|
func keptCount(m *memStore) int { k, _ := m.Keys(KeptState); return len(k) }
|
|
|
|
// 1826/K1: mesh-config takes what a pull request declares from the controller alone, and keeps it per pull request.
|
|
func TestAPullRequestsDeclarationsAreTakenFromTheControllerAlone(t *testing.T) {
|
|
c, m, _, _ := aHoldingMesh(t)
|
|
for _, caller := range []string{"g14.node-tools", Unproven, ""} {
|
|
if _, err := c.Do(Call{Action: "pull-declares", Pull: thePull, Head: "abc1234", Declares: n8nPullDeclares(),
|
|
Caller: caller}); err == nil || !strings.Contains(err.Error(), "controller") {
|
|
t.Errorf("pull-declares from %q: %v", caller, err)
|
|
}
|
|
}
|
|
for _, bad := range []Call{{Pull: "mesh-catalog#434", Head: "a"}, {Pull: thePull}, {Pull: "a/b#0", Head: "a"}} {
|
|
bad.Action, bad.Caller = "pull-declares", ControllerCaller
|
|
if _, err := c.Do(bad); err == nil {
|
|
t.Errorf("pull-declares %+v was taken", bad)
|
|
}
|
|
}
|
|
a := tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
if len(a.Modules) != 1 || a.Modules[0] != "n8n" {
|
|
t.Errorf("answered %+v", a)
|
|
}
|
|
e, _ := m.Get(PullsState, "novox.mesh-catalog.434")
|
|
var pd PullDecl
|
|
if e == nil || json.Unmarshal(e.Value, &pd) != nil || pd.Head != "abc1234def" ||
|
|
pd.Declares["n8n"]["media-gid"].Type != "number" || !pd.Declares["n8n"]["media-gid"].Required {
|
|
t.Errorf("kept %s", func() string {
|
|
if e == nil {
|
|
return "nothing"
|
|
}
|
|
return string(e.Value)
|
|
}())
|
|
}
|
|
// Nothing is resolved from a pull request's declaration.
|
|
if r, _ := m.Get(ResolvedState, ResolvedKey("ace", "n8n", "media-gid")); r != nil {
|
|
t.Errorf("a pull request's declaration was resolved: %s", r.Value)
|
|
}
|
|
}
|
|
|
|
// 1826/K2: a proposal for a key only the pull request declares is refused without its declaration, for a mesh or
|
|
// node level, for a clear, for an undeclared key, and for a module not placed on the node; and trunk-only
|
|
// proposals are judged as before.
|
|
func TestAProposalNamingAPullRequestIsJudgedAgainstItsDeclaration(t *testing.T) {
|
|
c, m, _, _ := aHoldingMesh(t)
|
|
try := func(call Call) error {
|
|
call.Action, call.Caller, call.Why = "propose", "g14.node-tools", "held for 434"
|
|
_, err := c.Propose(call)
|
|
return err
|
|
}
|
|
base := Call{Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull}
|
|
if err := try(base); err == nil || !strings.Contains(err.Error(), "holds no declaration for "+thePull) {
|
|
t.Errorf("without the pull request's declaration: %v", err)
|
|
}
|
|
// And without the pull argument it is the trunk's refusal, as before.
|
|
plain := base
|
|
plain.Pull = ""
|
|
if err := try(plain); err == nil || !strings.Contains(err.Error(), `n8n declares no value "media-gid"`) {
|
|
t.Errorf("the trunk's refusal changed: %v", err)
|
|
}
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
for name, tc := range map[string]struct {
|
|
mutate func(*Call)
|
|
want string
|
|
}{
|
|
"a mesh level": {func(c *Call) { c.Module, c.Node = "", "" }, "a module's own value on one node"},
|
|
"a node level": {func(c *Call) { c.Module = "" }, "a module's own value on one node"},
|
|
"a clear": {func(c *Call) { c.Clear, c.Value = true, nil }, "a clear is not held"},
|
|
"a key not added": {func(c *Call) { c.Key = "other" }, "does not add or change a value"},
|
|
"a bad value": {func(c *Call) { c.Value = 0.0 }, "below the minimum"},
|
|
"a wrong type": {func(c *Call) { c.Value = "2000" }, "is not a number"},
|
|
"another node": {func(c *Call) { c.Node = "g14" }, "is not placed on g14"},
|
|
"another pull": {func(c *Call) { c.Pull = "novox/mesh-catalog#435" }, "holds no declaration for"},
|
|
"a malformed pull": {func(c *Call) { c.Pull = "434" }, "owner/repository#number"},
|
|
} {
|
|
call := base
|
|
tc.mutate(&call)
|
|
if err := try(call); err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Errorf("%s: %v, want %q", name, err, tc.want)
|
|
}
|
|
}
|
|
// A set or a clear through the verb takes no pull request.
|
|
if _, err := c.Change(Call{Action: "set", Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull,
|
|
Caller: "g14.node-tools", Why: "w"}); err == nil || !strings.Contains(err.Error(), "only a proposal") {
|
|
t.Errorf("a set naming a pull request: %v", err)
|
|
}
|
|
if keptCount(m) != 0 {
|
|
t.Error("something was held")
|
|
}
|
|
}
|
|
|
|
// 1826/K2: every held value is asked whatever the key's trust; the ask says it is held and dropped on a close; on
|
|
// Approve the value is kept apart, never set or resolved; history, event and hand-act say it; held answers it.
|
|
func TestAHeldValueIsAskedAndKeptApartOnApprove(t *testing.T) {
|
|
c, m, r, _ := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
a := propose(t, c, Call{Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull, Why: "held for #434"})
|
|
if len(a.Reaches) != 0 {
|
|
t.Errorf("a held value reaches %v", a.Reaches)
|
|
}
|
|
ask := r.asks[a.Proposal]
|
|
if err := ask.Check(c.now()); err != nil {
|
|
t.Fatalf("the ask: %v", err)
|
|
}
|
|
for _, want := range []string{thePull, "merges", "dropped if it closes unmerged", "media-gid", "n8n", "ace"} {
|
|
if !strings.Contains(ask.Explanation, want) {
|
|
t.Errorf("the ask does not say %q:\n%s", want, ask.Explanation)
|
|
}
|
|
}
|
|
if strings.Contains(ask.Explanation, "takes effect at once") || !strings.Contains(ask.Headline, "Hold") {
|
|
t.Errorf("the ask: %q\n%s", ask.Headline, ask.Explanation)
|
|
}
|
|
if ap := ask.Options[0]; !strings.Contains(ap.Does, thePull) || ap.Level == "" {
|
|
t.Errorf("the approve option does %q", ap.Does)
|
|
}
|
|
if keptCount(m) != 0 {
|
|
t.Fatal("a proposal held something")
|
|
}
|
|
r.choose(t, a.Proposal, OptionApprove, nil)
|
|
said, _ := c.Decide()
|
|
if len(said) != 1 || !strings.Contains(said[0], "held at abc1234d for "+thePull) {
|
|
t.Fatalf("decided %v", said)
|
|
}
|
|
// Kept apart: not a value, not resolved.
|
|
if _, set := storedValue(t, m, "module.ace.n8n.media-gid"); set {
|
|
t.Error("an Approve set the value")
|
|
}
|
|
if e, _ := m.Get(ResolvedState, ResolvedKey("ace", "n8n", "media-gid")); e != nil {
|
|
t.Errorf("an Approve resolved the value: %s", e.Value)
|
|
}
|
|
if keptCount(m) != 1 {
|
|
t.Fatalf("%d values held", keptCount(m))
|
|
}
|
|
if len(m.events) != 1 || m.events[0].Action != ActionHold || m.events[0].Pull != thePull ||
|
|
m.events[0].Key != "media-gid" || m.events[0].Head != "abc1234def" || len(m.events[0].Moved) != 0 ||
|
|
!strings.Contains(m.events[0].Approved, "approved by the operator") {
|
|
t.Errorf("raised %+v", m.events)
|
|
}
|
|
if len(m.handActs) != 1 || !strings.Contains(m.handActs[0], thePull) {
|
|
t.Errorf("hand-acts %q", m.handActs)
|
|
}
|
|
h, _ := c.History(Call{Module: "n8n", Node: "ace"})
|
|
if len(h) != 1 || h[0].Action != ActionHold || h[0].Pull != thePull {
|
|
t.Errorf("history %+v", h)
|
|
}
|
|
// held answers it, at the head it is kept at, to the controller alone.
|
|
if _, err := c.Do(Call{Action: "held", Caller: "g14.node-tools", Pulls: []PullAt{{thePull, "abc1234def"}}}); err == nil {
|
|
t.Error("held answered a caller that is not the controller")
|
|
}
|
|
got, err := c.Do(Call{Action: "held", Caller: ControllerCaller, Pulls: []PullAt{{thePull, "abc1234def"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ha := got.(*HeldAnswer)
|
|
if len(ha.Held) != 1 || ha.Held[0].Module != "n8n" || ha.Held[0].Node != "ace" || ha.Held[0].Key != "media-gid" ||
|
|
string(ha.Held[0].Value) != "2000" || !strings.Contains(ha.Held[0].Approved, "approved by the operator") ||
|
|
ha.Held[0].Pull != thePull || len(ha.Skipped) != 0 {
|
|
t.Errorf("held answered %+v", ha)
|
|
}
|
|
// Another head, and another pull request, are not answered.
|
|
got, _ = c.Do(Call{Action: "held", Caller: ControllerCaller,
|
|
Pulls: []PullAt{{thePull, "ffff0000"}, {"novox/mesh-catalog#435", "abc"}}})
|
|
if ha = got.(*HeldAnswer); len(ha.Held) != 0 || len(ha.Skipped) != 1 || !strings.Contains(ha.Skipped[0].Why, "held at") {
|
|
t.Errorf("held at another head answered %+v", ha)
|
|
}
|
|
// The listing shows it as held, and the proposal as held for the pull request.
|
|
l, _ := c.List(Call{Module: "n8n"})
|
|
if len(l.Held) != 1 || len(l.Set) != 0 {
|
|
t.Errorf("the listing: held %+v set %+v", l.Held, l.Set)
|
|
}
|
|
rows, _ := c.Proposals(Call{})
|
|
if len(rows) != 1 || rows[0].Pull != thePull || rows[0].State != ProposalApproved {
|
|
t.Errorf("proposals %+v", rows)
|
|
}
|
|
}
|
|
|
|
// 1826/K2: an Approve is refused when the pull request's declaration changed after the operator was shown it.
|
|
func TestAnApproveOfAHeldValueIsRefusedWhenTheDeclarationChanged(t *testing.T) {
|
|
c, m, r, _ := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
a := propose(t, c, Call{Module: "n8n", Node: "ace", Key: "media-gid", Value: 2000.0, Pull: thePull, Why: "w"})
|
|
changed := n8nPullDeclares()
|
|
changed["n8n"].(map[string]any)["media-gid"].(map[string]any)["maximum"] = 1000.0
|
|
tell(t, c, thePull, "def5678abc", changed)
|
|
r.choose(t, a.Proposal, OptionApprove, nil)
|
|
said, _ := c.Decide()
|
|
if len(said) != 1 || !strings.Contains(said[0], "nothing is held") || keptCount(m) != 0 {
|
|
t.Errorf("decided %v; %d held", said, keptCount(m))
|
|
}
|
|
}
|
|
|
|
// 1826/K1: at a new head a held value is kept if its key is declared the same and it still passes; otherwise it is
|
|
// dropped, recorded, raised and said.
|
|
func TestANewHeadKeepsAHeldValueOnlyWhileItsDeclarationIsUnchanged(t *testing.T) {
|
|
c, m, r, _ := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
holdIt(t, c, m, r)
|
|
m.events = nil
|
|
|
|
// The same declaration at a new head (the description may differ): kept, moved to the new head.
|
|
same := n8nPullDeclares()
|
|
same["n8n"].(map[string]any)["media-gid"].(map[string]any)["description"] = "reworded"
|
|
if a := tell(t, c, thePull, "1111111111", same); len(a.Dropped) != 0 {
|
|
t.Errorf("an unchanged declaration dropped %v", a.Dropped)
|
|
}
|
|
got, _ := c.Do(Call{Action: "held", Caller: ControllerCaller, Pulls: []PullAt{{thePull, "1111111111"}}})
|
|
if len(got.(*HeldAnswer).Held) != 1 || len(m.events) != 0 {
|
|
t.Errorf("after an unchanged head: %+v, events %+v", got, m.events)
|
|
}
|
|
|
|
// A changed declaration: dropped.
|
|
changed := n8nPullDeclares()
|
|
changed["n8n"].(map[string]any)["media-gid"].(map[string]any)["maximum"] = 1000.0
|
|
a := tell(t, c, thePull, "2222222222", changed)
|
|
if len(a.Dropped) != 1 || !strings.Contains(a.Dropped[0], "declares the key otherwise") || keptCount(m) != 0 {
|
|
t.Fatalf("a changed declaration: %+v, %d held", a, keptCount(m))
|
|
}
|
|
if len(m.events) != 1 || m.events[0].Action != ActionDrop || m.events[0].Pull != thePull {
|
|
t.Errorf("raised %+v", m.events)
|
|
}
|
|
got, _ = c.Do(Call{Action: "held", Caller: ControllerCaller, Pulls: []PullAt{{thePull, "2222222222"}}})
|
|
if len(got.(*HeldAnswer).Held) != 0 {
|
|
t.Error("a dropped value was answered")
|
|
}
|
|
|
|
// A head that no longer adds the key drops it too.
|
|
tell(t, c, thePull, "3333333333", n8nPullDeclares())
|
|
holdIt(t, c, m, r)
|
|
if a = tell(t, c, thePull, "4444444444", map[string]any{}); len(a.Dropped) != 1 ||
|
|
!strings.Contains(a.Dropped[0], "no longer adds or changes") {
|
|
t.Errorf("a head without the key: %+v", a)
|
|
}
|
|
}
|
|
|
|
// 1826/K3: the merge writes the held value as the module's own value with its approval kept, before the trunk
|
|
// declares the key; it resolves right after the declared-config mirror moves.
|
|
func TestTheMergeAppliesAHeldValueAndItResolvesOnceTheTrunkDeclaresTheKey(t *testing.T) {
|
|
c, m, r, _ := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
holdIt(t, c, m, r)
|
|
approved := func() string {
|
|
raw, _ := m.Get(KeptState, "novox.mesh-catalog.434.ace.n8n.media-gid")
|
|
var k KeptValue
|
|
_ = json.Unmarshal(raw.Value, &k)
|
|
return k.Approved
|
|
}()
|
|
m.events, m.handActs = nil, nil
|
|
|
|
said := c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: "abc1234def"}, true)
|
|
if len(said) == 0 {
|
|
t.Fatal("the merge said nothing")
|
|
}
|
|
v, set := storedValue(t, m, "module.ace.n8n.media-gid")
|
|
if !set || string(v.Value) != "2000" || v.Approved != approved || v.Approved == "" || v.By != "the operator" ||
|
|
!strings.Contains(v.Why, thePull) {
|
|
t.Fatalf("the merge wrote %+v", v)
|
|
}
|
|
if keptCount(m) != 0 {
|
|
t.Error("the value is still held after the merge")
|
|
}
|
|
if p, _ := m.Keys(PullsState); len(p) != 0 {
|
|
t.Errorf("the pull request's declarations are still kept: %v", p)
|
|
}
|
|
if len(m.events) != 1 || m.events[0].Action != "set" || m.events[0].Pull != thePull ||
|
|
m.events[0].Approved != approved || len(m.handActs) != 1 {
|
|
t.Errorf("raised %+v, recorded %q", m.events, m.handActs)
|
|
}
|
|
// Not resolved before the trunk declares it.
|
|
if e, _ := m.Get(ResolvedState, ResolvedKey("ace", "n8n", "media-gid")); e != nil {
|
|
t.Errorf("resolved before the trunk declares the key: %s", e.Value)
|
|
}
|
|
m.declare(t, "n8n", `{"module":"n8n","settings":{"media-gid":{"kind":"","default":null,"why":"",`+
|
|
`"type":"number","required":true,"minimum":1,"maximum":65535}}}`)
|
|
if _, err := c.Reconcile(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res := m.resolved(t, "ace", "n8n", "media-gid"); string(res.Value) != "2000" || res.Source != SourceModule {
|
|
t.Errorf("media-gid resolves as %+v", res)
|
|
}
|
|
l, _ := c.List(Call{Module: "n8n", Node: "ace"})
|
|
if len(l.Set) != 1 || l.Set[0].Approved != approved || len(l.Held) != 0 {
|
|
t.Errorf("the listing: set %+v held %+v", l.Set, l.Held)
|
|
}
|
|
// A merge heard twice sets nothing twice.
|
|
m.events = nil
|
|
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: "abc1234def"}, true)
|
|
if len(m.events) != 0 {
|
|
t.Errorf("a second merge raised %+v", m.events)
|
|
}
|
|
}
|
|
|
|
// 1826/K3: a pull request closed unmerged drops its held values and says so; nothing is set or resolved.
|
|
func TestAClosedPullRequestDropsItsHeldValuesAndSaysSo(t *testing.T) {
|
|
c, m, r, f := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
holdIt(t, c, m, r)
|
|
m.events = nil
|
|
f.state[thePull] = [2]bool{false, false}
|
|
said := c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434}, false)
|
|
if len(said) == 0 || !strings.Contains(strings.Join(said, "\n"), "closed unmerged: dropped, nothing was set") {
|
|
t.Errorf("said %v", said)
|
|
}
|
|
if _, set := storedValue(t, m, "module.ace.n8n.media-gid"); set || keptCount(m) != 0 {
|
|
t.Error("a closed pull request left a value")
|
|
}
|
|
if len(m.events) != 1 || m.events[0].Action != ActionDrop || m.events[0].Pull != thePull ||
|
|
!strings.Contains(m.events[0].Why, "closed unmerged") {
|
|
t.Errorf("raised %+v", m.events)
|
|
}
|
|
if p, _ := m.Keys(PullsState); len(p) != 0 {
|
|
t.Errorf("declarations of a closed pull request kept: %v", p)
|
|
}
|
|
}
|
|
|
|
// 1826/K3: a close event for a pull request the forge says merged is the merge.
|
|
func TestACloseEventForAMergedPullRequestIsItsMerge(t *testing.T) {
|
|
c, m, r, f := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
holdIt(t, c, m, r)
|
|
f.state[thePull] = [2]bool{false, true}
|
|
f.head = "abc1234def"
|
|
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434}, false)
|
|
if v, set := storedValue(t, m, "module.ace.n8n.media-gid"); !set || string(v.Value) != "2000" {
|
|
t.Errorf("the merge of a pull request announced closed set %+v", v)
|
|
}
|
|
}
|
|
|
|
// 1826/K3: the five-minute look finds a merge, a close, and an open pull request, and says a forge that does not
|
|
// answer.
|
|
func TestTheLookAtTheForgeFindsAMissedMergeOrCloseAndLeavesAnOpenPullRequest(t *testing.T) {
|
|
c, m, r, f := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
holdIt(t, c, m, r)
|
|
if said := c.SweepPulls(); len(said) != 0 || keptCount(m) != 1 {
|
|
t.Errorf("an open pull request: said %v, %d held", said, keptCount(m))
|
|
}
|
|
f.err = errors.New("the forge is down")
|
|
if said := c.SweepPulls(); len(said) != 1 || !strings.Contains(said[0], "does not say whether") || keptCount(m) != 1 {
|
|
t.Errorf("a silent forge: said %v", said)
|
|
}
|
|
f.err = nil
|
|
f.head = "abc1234def"
|
|
f.state[thePull] = [2]bool{false, true}
|
|
c.SweepPulls()
|
|
if v, set := storedValue(t, m, "module.ace.n8n.media-gid"); !set || string(v.Value) != "2000" || keptCount(m) != 0 {
|
|
t.Errorf("a missed merge: %+v", v)
|
|
}
|
|
// A missed close.
|
|
tell(t, c, "novox/mesh-catalog#440", "abc", n8nPullDeclares())
|
|
f.state["novox/mesh-catalog#440"] = [2]bool{false, false}
|
|
c.SweepPulls()
|
|
if p, _ := m.Keys(PullsState); len(p) != 0 {
|
|
t.Errorf("a missed close left %v", p)
|
|
}
|
|
}
|
|
|
|
// 1826/K1: the new arguments are read strictly and the new actions are the verb's.
|
|
func TestThePullArgumentsAreRead(t *testing.T) {
|
|
call, err := callOf(map[string]any{"action": "pull-declares", "pull": " " + thePull + " ", "head": "abc",
|
|
"declares": map[string]any{"n8n": map[string]any{}}, "pulls": []any{map[string]any{"pull": thePull, "head": "h"}}})
|
|
if err != nil || call.Pull != thePull || call.Head != "abc" || len(call.Declares) != 1 || len(call.Pulls) != 1 ||
|
|
call.Pulls[0].Head != "h" {
|
|
t.Errorf("read %+v (%v)", call, err)
|
|
}
|
|
if _, err := callOf(map[string]any{"declares": "x"}); err == nil {
|
|
t.Error("declares that is not an object was read")
|
|
}
|
|
if _, err := callOf(map[string]any{"pulls": "x"}); err == nil {
|
|
t.Error("pulls that is not a list was read")
|
|
}
|
|
if _, err := callOf(map[string]any{"bogus": 1}); err == nil || !strings.Contains(err.Error(), "pull") {
|
|
t.Errorf("an unknown argument: %v", err)
|
|
}
|
|
_ = time.Second
|
|
}
|
|
|
|
// 1826 review r1: the merge applies a value only if it is held at the merged head; otherwise it is dropped and said.
|
|
func TestTheMergeAppliesOnlyAValueHeldAtTheMergedHead(t *testing.T) {
|
|
for name, head := range map[string]string{"another head": "9999999999", "no head told": ""} {
|
|
c, m, r, _ := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
holdIt(t, c, m, r)
|
|
said := c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: head}, true)
|
|
if _, set := storedValue(t, m, "module.ace.n8n.media-gid"); set || keptCount(m) != 0 ||
|
|
!strings.Contains(strings.Join(said, "\n"), "dropped, nothing was set") {
|
|
t.Errorf("%s: set=%v held=%d said %v", name, set, keptCount(m), said)
|
|
}
|
|
if len(m.events) == 0 || m.events[len(m.events)-1].Action != ActionDrop {
|
|
t.Errorf("%s: raised %+v", name, m.events)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1826 review r1: a later head without the declaration drops the value, so the merge has nothing to apply.
|
|
func TestALaterHeadWithoutTheDeclarationLeavesNothingToApplyAtTheMerge(t *testing.T) {
|
|
c, m, r, _ := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
holdIt(t, c, m, r)
|
|
tell(t, c, thePull, "bbbbbbbbbb", map[string]any{})
|
|
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434, Merged: true, HeadSHA: "bbbbbbbbbb"}, true)
|
|
if _, set := storedValue(t, m, "module.ace.n8n.media-gid"); set {
|
|
t.Error("a value held for a declaration the merged head lacks was set")
|
|
}
|
|
}
|
|
|
|
// 1826 review r1: a close the forge cannot be asked about drops nothing; the five-minute look decides.
|
|
func TestACloseThatTheForgeCannotBeAskedAboutDropsNothing(t *testing.T) {
|
|
c, m, r, f := aHoldingMesh(t)
|
|
tell(t, c, thePull, "abc1234def", n8nPullDeclares())
|
|
holdIt(t, c, m, r)
|
|
f.err = errors.New("the forge is down")
|
|
c.HeardPull(PullEvent{Owner: "novox", Repo: "mesh-catalog", Number: 434}, false)
|
|
if keptCount(m) != 1 {
|
|
t.Fatal("a close the forge could not confirm dropped the value")
|
|
}
|
|
f.err = nil
|
|
f.state[thePull] = [2]bool{false, false}
|
|
c.SweepPulls()
|
|
if keptCount(m) != 0 {
|
|
t.Error("the look did not drop it")
|
|
}
|
|
}
|