77 lines
4.1 KiB
Go
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)
|
|
}
|
|
}
|