A controller restart lost every call's outcome, `status` composed the mesh while its caller waited (18.6s live on 2026-10-06, past the 10s window), a repair by hand left no trace, and the core's bounds had nothing measured to be set from. - calls: kept in the controller's bucket mesh-controller_calls (last 1000 or 14 days, answers bounded to 64 KiB), read by id across a restart; a controller starting marks a stopped one's running calls abandoned; each call names its caller from the inbox its answer goes to. - status: the serving controller composes it at start, after news from a machine, a build or an acting verb, and every minute; the verb answers the last composition at once with when and how long it took. Composing resolves each machine once instead of twice. - hand-act log in mesh-controller_hand-acts: push (required through the seat), plans stop/close, broker consumer-reset and the new hand-act record take --why/--cause/--condition; `hand-acts` lists them and repeated causes; status counts the week's. - durations (migration 0066): apply (send to first report), heartbeat gap, plan tier and build, recorded as heard; `durations` summarises them. - the controller's seat row takes this binary's definition of its own verbs, so the console no longer judges calls against an older build's schema. - the controller is granted its two buckets' subjects.
60 lines
2.1 KiB
Go
60 lines
2.1 KiB
Go
package link
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"github.com/novox/mesh-controller/internal/broker"
|
|
)
|
|
|
|
// The hand-act log against a real server (novox/hq to-be 45 §7): an act is written with who, why and
|
|
// its cause, read back in order, and a cause recorded twice within a fortnight is found.
|
|
func TestNatsAnActByHandIsKeptWithWhyAndARepeatIsFound(t *testing.T) {
|
|
js := aBus(t)
|
|
api, err := jetstream.New(js.Conn())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = api.DeleteKeyValue(t.Context(), broker.HandActsBucket)
|
|
if err := js.EnsureControllerBuckets(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv(CallerVar, "node-tools.g14, through the mesh-controller seat")
|
|
if _, err := RecordHandAct(t.Context(), js.Conn(), HandAct{Verb: "push", Args: []string{"anchor"}}); err == nil {
|
|
t.Fatal("an act without why was written")
|
|
}
|
|
first, err := RecordHandAct(t.Context(), js.Conn(), HandAct{Verb: "push", Args: []string{"anchor"},
|
|
Why: "it never reported the send", Cause: "sent-not-reported"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if first.By != "node-tools.g14, through the mesh-controller seat" || first.ID == "" {
|
|
t.Fatalf("written as %+v", first)
|
|
}
|
|
if _, err := RecordHandAct(t.Context(), js.Conn(), HandAct{Verb: "plans close", Args: []string{"plan-1"},
|
|
Why: "waiting on the same report"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := RecordHandAct(t.Context(), js.Conn(), HandAct{Verb: "push", Args: []string{"ace"},
|
|
Why: "again", Cause: "sent-not-reported", At: time.Now().UTC().Add(time.Second)}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
acts, err := HandActs(t.Context(), js.Conn(), time.Now().Add(-time.Hour))
|
|
if err != nil || len(acts) != 3 || acts[0].ID != first.ID || acts[1].Cause != "plans close" {
|
|
t.Fatalf("%v %+v", err, acts)
|
|
}
|
|
repeated := RepeatedCauses(acts, time.Now())
|
|
if len(repeated) != 1 || repeated["sent-not-reported"] != 2 {
|
|
t.Fatalf("repeated %v", repeated)
|
|
}
|
|
if old, _ := HandActs(t.Context(), js.Conn(), time.Now().Add(time.Hour)); len(old) != 0 {
|
|
t.Fatalf("acts before the moment asked were listed: %+v", old)
|
|
}
|
|
if !strings.HasPrefix(acts[2].ID, "act-") {
|
|
t.Fatalf("%q", acts[2].ID)
|
|
}
|
|
}
|