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 node-a 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.mirror(t, DeclaredConfigRead, "n8n", `{"module":"n8n","settings":{}}`)
|
|
m.assign(t, "node-a", "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 node-a 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: "node-a", 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{"node-b.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("node-a", "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 assigned to 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", "node-b.node-tools", "held for 434"
|
|
_, err := c.Propose(call)
|
|
return err
|
|
}
|
|
base := Call{Module: "n8n", Node: "node-a", 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 = "node-b" }, "is not assigned to node-b"},
|
|
"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: "node-a", Key: "media-gid", Value: 2000.0, Pull: thePull,
|
|
Caller: "node-b.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: "node-a", 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", "node-a"} {
|
|
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.node-a.n8n.media-gid"); set {
|
|
t.Error("an Approve set the value")
|
|
}
|
|
if e, _ := m.Get(ResolvedState, ResolvedKey("node-a", "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: "node-a"})
|
|
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: "node-b.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 != "node-a" || 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: "node-a", 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.node-a.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.node-a.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("node-a", "n8n", "media-gid")); e != nil {
|
|
t.Errorf("resolved before the trunk declares the key: %s", e.Value)
|
|
}
|
|
m.mirror(t, DeclaredConfigRead, "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, "node-a", "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: "node-a"})
|
|
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.node-a.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.node-a.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.node-a.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.node-a.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.node-a.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")
|
|
}
|
|
}
|