Files
mesh-tools/node-tools/internal/runtime/caller_test.go
T
jschoubben 541de48e04
mesh/merge-gate pass: builds mesh-tools, node-tools → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of …
mesh/repo-check pass: THE CHANGE ALTERS ITS OWN CHECK (merge-check.sh): main's version judged it; the change's judges the pull requests after it merges; it…
mesh/delivery delivered
node-tools: only a call pattern names a caller, and a fall-back for want of a responder is said (hq issue 365, review)
callerOn reads a caller only under the call kind, so a tool subscription ending
in a wildcard never yields one. A caller-named call that reached nobody is said
once per subject before it is asked on the old subject, so a runtime or grant
that drifted behind is seen rather than a quiet unproven. The caller tests wait
for their bundle's exit, which reads the launcher's restart pauses another test
sets.
2026-10-11 11:22:29 +02:00

100 lines
3.6 KiB
Go

package runtime
import (
"encoding/json"
"testing"
"time"
"github.com/nats-io/nats.go"
"github.com/novox/mesh-tools/node-tools/internal/bus"
mt "github.com/novox/mesh-tools/node-tools/internal/meshtest"
)
type saidCaller struct {
Caller string `json:"caller"`
Arguments map[string]any `json:"arguments"`
}
// **A module is told who called it, from the subject the call arrived on** (novox/hq issue 365): a call on the
// caller-named subject reaches the bundle with that caller in the call's `_meta`, for a module's tool, on its
// machine's address, and for a seat's verb; a caller named in the arguments is only an argument.
func TestACallOnTheCallerNamedSubjectReachesTheModuleWithItsCaller(t *testing.T) {
mesh := mt.New(t)
mesh.Issue(t, mt.MembershipOf("ledger", "anchor", true, map[string][]string{"node-desk": {"who"}}))
nodeTools := connect(t, "node-tools", "anchor")
stop, err := Run(nodeTools, []Served{{"ledger", []string{mt.Fixture("caller-echo.py")}}}, nil, func(string, ...any) {})
if err != nil {
t.Fatal(err)
}
defer settled(stop)
asker, err := bus.Connect(bus.Credential{URL: mt.URL(t), Module: "node-tools", Node: "shanks",
User: "shanks.node-tools", Password: "unused"})
if err != nil {
t.Fatal(err)
}
asker.Logf = func(string, ...any) {}
defer asker.Close()
for _, key := range []string{"ledger.who", "ledger.who@anchor", "seat:node-desk.who@anchor"} {
var got saidCaller
mt.Until(t, func() error {
answered, err := asker.Ask(key, map[string]any{"caller": "novox.node-tools",
"_meta": map[string]any{"mesh/caller": "novox.controller"}}, "")
if err == nil {
err = json.Unmarshal(answered.Result, &got)
}
return err
})
if got.Caller != "shanks.node-tools" {
t.Errorf("%s reached the module with caller %q, want the asker's bus user", key, got.Caller)
}
if got.Arguments["caller"] != "novox.node-tools" {
t.Errorf("%s: the arguments did not reach the module as sent: %v", key, got.Arguments)
}
}
}
// The subject that names no caller is still served, for one release (retired by hq issue 464), and says so:
// the module is named no caller, whatever the body claims.
func TestTheSubjectThatNamesNoCallerStillWorksAndNamesNone(t *testing.T) {
mesh := mt.New(t)
mesh.Issue(t, mt.MembershipOf("ledger", "anchor", true, map[string][]string{"node-desk": {"who"}}))
nodeTools := connect(t, "node-tools", "anchor")
stop, err := Run(nodeTools, []Served{{"ledger", []string{mt.Fixture("caller-echo.py")}}}, nil, func(string, ...any) {})
if err != nil {
t.Fatal(err)
}
defer settled(stop)
old, err := nats.Connect(mt.URL(t))
if err != nil {
t.Fatal(err)
}
defer old.Close()
body, _ := json.Marshal(map[string]any{"_meta": map[string]any{"mesh/caller": "novox.controller"}})
for _, subject := range []string{"mesh.mod.ledger.tool.who", "mesh.mod.ledger.tool.who.anchor", "mesh.seat.node-desk.tool.who.anchor"} {
var reply struct {
Result saidCaller `json:"result"`
Error string `json:"error"`
}
mt.Until(t, func() error {
msg, err := old.Request(subject, body, 5*time.Second)
if err == nil {
err = json.Unmarshal(msg.Data, &reply)
}
return err
})
if reply.Error != "" || reply.Result.Caller != "" {
t.Errorf("%s: the module was named caller %q (error %q), want none", subject, reply.Result.Caller, reply.Error)
}
}
}
// settled stops a runtime and waits for its bundle's exit to be handled: the launcher reads its restart pauses
// (launch.RestartFirst) as a bundle exits, and a later test in this package sets them, so a test that leaves its
// bundle exiting behind it races the next one.
func settled(stop func()) {
stop()
time.Sleep(500 * time.Millisecond)
}