Files
mesh-catalog/modules/messenger/cmd/messenger/motion_test.go
T

77 lines
4.1 KiB
Go

package main
import (
"testing"
"time"
)
// R6 (ADR 0539 §17): plans and their plan-moved are gone, so a walk is a module in motion on a node, as the delivery view says
// it: `moving`, then `settled` or `stuck`. The holding, the bound, the settle and the end message are the walk's, unchanged. The
// subjects and bodies are the view's as the lead relayed them (node, module, from_fp, to_fp, composed, since, bound_seconds).
// motion says a view event to the router, through the decoder the bus's events go through.
func motion(t *testing.T, h *Holder, event, body string) {
t.Helper()
ev, ok, err := DecodeMotion(event, []byte(body))
if err != nil || !ok {
t.Fatalf("the %s event was not read: %v %v", event, ok, err)
}
h.Walked(ev)
}
func TestTheViewsEventsAreReadAsAWalkOfOneModuleOnOneNode(t *testing.T) {
since := time.Date(2026, 10, 24, 12, 0, 0, 0, time.UTC)
start, ok, err := DecodeMotion(EventMoving, []byte(`{"node":"ace","module":"node-tools","from_fp":"a","to_fp":"b","composed":"c",`+
`"since":"2026-10-24T12:00:00Z","bound_seconds":600}`))
if err != nil || !ok || start.Walk != "ace/node-tools" || start.Phase != WalkStarted || start.BoundSeconds != 600 ||
!start.StartedAt.Equal(since) || len(start.Touches) != 1 || start.Touches[0].Machine != "ace" || start.Touches[0].Modules[0] != "node-tools" {
t.Fatalf("moving read as %+v %v %v", start, ok, err)
}
end, ok, err := DecodeMotion(EventSettled, []byte(`{"node":"ace","module":"node-tools","at":"2026-10-24T12:05:00Z"}`))
if err != nil || !ok || end.Walk != "ace/node-tools" || end.Phase != WalkEnded || end.Outcome != WalkDelivered {
t.Fatalf("settled read as %+v %v %v", end, ok, err)
}
stuck, _, _ := DecodeMotion(EventStuck, []byte(`{"node":"ace","module":"node-tools","outcome":"stopped","words":"held for a backup"}`))
failed, _, _ := DecodeMotion(EventStuck, []byte(`{"node":"ace","module":"node-tools","words":"unhealthy"}`))
if stuck.Outcome != WalkStopped || stuck.Words != "held for a backup" || failed.Outcome != WalkFailed {
t.Fatalf("stuck read as %+v and %+v", stuck, failed)
}
if _, ok, _ := DecodeMotion("other", []byte(`{}`)); ok {
t.Fatal("an event that is no motion was read as one")
}
if _, _, err := DecodeMotion(EventMoving, []byte(`{"module":"x"}`)); err == nil {
t.Fatal("a motion that names no node was read")
}
}
func TestAModuleInMotionHoldsWhatItTouchesAndIsToldAtSettle(t *testing.T) {
h, tg, dt, c, _ := newHolder(t)
motion(t, h, EventMoving, `{"node":"novox","module":"node-tools","from_fp":"a","to_fp":"b","since":"`+c.now().Format(time.RFC3339Nano)+`","bound_seconds":600}`)
k := cond("module.node-tools.novox.unhealthy", Urgent, "the tool runner on novox does not answer")
k.Machine, k.Raised = "novox", c.now()
h.Condition(EventRaised, k)
runFor(h, c, time.Minute)
if saidOn(tg, dt) != 0 || !has(outcomes(h, k.Key), "held") {
t.Fatalf("a module in motion did not hold its condition: %v", outcomes(h, k.Key))
}
motion(t, h, EventSettled, `{"node":"novox","module":"node-tools","at":"`+c.now().Format(time.RFC3339Nano)+`"}`)
runFor(h, c, DefaultSettle+time.Minute)
if !heard("Delivered: node-tools on novox", tg, dt) || !heard("Still open after the delivery of node-tools to novox", tg, dt) {
t.Fatalf("the settle did not tell the end and what is still open: %+v %+v", tg.sends, dt.sends)
}
}
func TestAStuckModuleIsToldAtOnceWithTheViewsWords(t *testing.T) {
h, tg, dt, c, _ := newHolder(t)
motion(t, h, EventMoving, `{"node":"novox","module":"node-tools","since":"`+c.now().Format(time.RFC3339Nano)+`","bound_seconds":600}`)
k := cond("machine.novox.tools-silent", Warning, "the tools on novox are silent")
k.Raised = c.now()
h.Condition(EventRaised, k)
runFor(h, c, time.Minute)
motion(t, h, EventStuck, `{"node":"novox","module":"node-tools","outcome":"failed","words":"the new build did not come healthy on novox","at":"`+c.now().Format(time.RFC3339Nano)+`"}`)
runFor(h, c, time.Minute)
if !heard("Delivery failed: node-tools on novox", tg, dt) || !heard("the new build did not come healthy on novox", tg, dt) {
t.Fatalf("a stuck module was not told at once with the view's words: %+v %+v", tg.sends, dt.sends)
}
}