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

87 lines
4.0 KiB
Go

//go:build integration
package main
import (
"encoding/json"
"strings"
"testing"
"time"
)
// novox/hq ADR 0505 over the real bus, in the bed of condition_bus_test.go: the router whole, its walks kept in its
// state, the controller's walk and condition events handed to it as the runtime hands them.
// controllerEvent hands the bundle one of the controller's events, as the runtime does.
func (b *conditionBed) controllerEvent(t *testing.T, event string, body any) {
t.Helper()
raw, err := json.Marshal(body)
if err != nil {
t.Fatal(err)
}
if _, err := b.r.request("mesh/event", map[string]any{"envelope": map[string]any{"key": ControllerSeat + "." + event,
"node": "test", "body": json.RawMessage(raw)}}); err != nil {
t.Fatal(err)
}
}
// walkEvent is a plan-moved body as the controller says a walk's start or end (mesh-controller walk_said.go), with a
// machine it sends nothing to and whose declaration its merges change (`declared`, ADR 0505 §2b).
func walkEvent(phase, outcome string, started, at time.Time) map[string]any {
b := map[string]any{"id": "plan-bus", "state": "walking", "walk": "plan-bus", "phase": phase, "budget_class": "leaf",
"bound_seconds": 600, "started_at": started.UTC().Format(time.RFC3339Nano),
"touches": []map[string]any{{"machine": "novox", "modules": []string{"gitea"}},
{"machine": "shanks", "modules": []string{"mesh-build-image"}, "declared": true}}}
if phase == WalkEnded {
b["outcome"], b["ended_at"] = outcome, at.UTC().Format(time.RFC3339Nano)
}
return b
}
// conditionEvent is a condition as the controller raises it (mesh-controller probes.go D1, signals.go).
func conditionEvent(key, scope, id, machine, headline string) map[string]any {
now := time.Now().UTC().Format(time.RFC3339)
return map[string]any{"key": key, "kind": key[strings.LastIndex(key, ".")+1:], "severity": Urgent,
"subject": map[string]string{"scope": scope, "id": id, "machine": machine}, "summary": headline,
"headline": headline, "explanation": "Needs you: look at it. " + headline + ".", "needs": "look at it.",
"raised": now, "at": now}
}
func (b *conditionBed) toldWith(words string) *string {
for _, m := range b.told() {
if strings.Contains(m.Title+"\n"+m.Body, words) {
text := m.Title + "\n" + m.Body
return &text
}
}
return nil
}
// ADR 0505 §1, §2b, §3: during a walk, a condition on a machine whose declaration it changes is held, and one on an
// untouched machine reaches the desk at once ending "No delivery running."; after the walk delivered, the held one
// reaches the desk only once the settle (two seconds here) has passed, inside the walk's end message.
func TestAWalksDeclaredMachineIsHeldUntilItSettledOverTheBus(t *testing.T) {
b := newConditionBedWith(t, `{"settle-seconds": 2}`)
started := time.Now()
b.controllerEvent(t, EventPlanMoved, walkEvent(WalkStarted, "", started, started))
b.controllerEvent(t, EventRaised, conditionEvent("machine.shanks.uncomposable", "machine", "shanks", "shanks",
"Shanks cannot get new instructions"))
b.controllerEvent(t, EventRaised, conditionEvent("machine.ace.silent", "machine", "ace", "ace", "Ace is silent"))
b.r.until("the untouched machine's condition reaches the desk with its delivery line", 15*time.Second, func() bool {
m := b.toldWith("Ace is silent")
return m != nil && strings.HasSuffix(*m, "\nNo delivery running.")
})
if b.toldWith("Shanks cannot get new instructions") != nil {
t.Fatalf("the declared machine's condition was told during the walk")
}
b.controllerEvent(t, EventPlanMoved, walkEvent(WalkEnded, WalkDelivered, started, time.Now()))
time.Sleep(time.Second)
if b.toldWith("Shanks cannot get new instructions") != nil || b.toldWith("Delivered") != nil {
t.Fatalf("told before the settle passed: %+v", b.told())
}
b.r.until("the walk's end tells what it held once it settled", 30*time.Second, func() bool {
m := b.toldWith("Still open after the delivery of gitea to novox:")
return m != nil && strings.Contains(*m, "Shanks cannot get new instructions") && !strings.Contains(*m, "shanks,")
})
}