mesh/delivery merged: source.main moved to novox/mesh-sdk@7c015b652d36
mesh/delivery-group group feat/sdk1 merged: every member is merged
mesh/check failed: the composition: the composer at mesh-sdk f02f142af128b336c32a7f0059c5e1606bf9b1dc did not run: failed the composer git.novox.be/no…
The check of release sdk1 failed on TestStatePutWritesAKeyWhole: TestACallIsAskedAgainOnlyWhenItReachedNoOne left busretry's Bound at 50 ms, and on a loaded machine the state test's first pause overran it. Second failure of the kind "tests leave package-level state set". Quick fix: restore busretry's waits in the one test and lengthen the state test's bound. Thorough fix (chosen, the kind met twice): busretry.Waits sets the waits and returns their restore, used as t.Cleanup(busretry.Waits(...)) by every test that sets them, each with a bound of seconds where it waits to succeed; and servesAlone gives every test that serves its own runtime channel and Served, put back after it, instead of tests clearing the channel an earlier one left. A scan of every test assignment to a package-level variable found no other unrestored one. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
127 lines
4.8 KiB
Go
127 lines
4.8 KiB
Go
package process
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/busretry"
|
|
)
|
|
|
|
// 1191/AC3: given a module tool key <module>.<tool>, when called, then the subject is
|
|
// mesh.mod.<module>.call.<tool>.<token> with the same fallback (mesh.mod.<module>.tool.<tool>).
|
|
func TestCallSubjectsForAModuleTool(t *testing.T) {
|
|
called, fallback, err := callSubjects("mesh-mail.send", "novox~mesh-mail~send")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if called != "mesh.mod.mesh-mail.call.send.novox~mesh-mail~send" {
|
|
t.Errorf("called: %q", called)
|
|
}
|
|
if fallback != "mesh.mod.mesh-mail.tool.send" {
|
|
t.Errorf("fallback: %q", fallback)
|
|
}
|
|
}
|
|
|
|
// 1191/AC1's subject (the seat form): seat:<seat>.<verb> calls mesh.seat.<seat>.call.<verb>.<token>,
|
|
// falling back to mesh.seat.<seat>.tool.<verb>.
|
|
func TestCallSubjectsForASeatVerb(t *testing.T) {
|
|
called, fallback, err := callSubjects("seat:operator-channel.tell", "novox~mesh-mail~send")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if called != "mesh.seat.operator-channel.call.tell.novox~mesh-mail~send" {
|
|
t.Errorf("called: %q", called)
|
|
}
|
|
if fallback != "mesh.seat.operator-channel.tool.tell" {
|
|
t.Errorf("fallback: %q", fallback)
|
|
}
|
|
}
|
|
|
|
func TestCallSubjectsRefusesAKeyNamingNeither(t *testing.T) {
|
|
for _, key := range []string{"", "nodot", "seat:", "seat:noverb", "."} {
|
|
if _, _, err := callSubjects(key, "token"); err == nil {
|
|
t.Errorf("callSubjects(%q) was accepted", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1470/AC1: key mesh-build-go.fetch, node ace, caller ace.mesh-delivery-builder.
|
|
func TestCallSubjectsOnANodeForAModuleTool(t *testing.T) {
|
|
c := &Conn{account: Account{User: "ace.mesh-delivery-builder"}}
|
|
called, fallback, err := callSubjectsOn("mesh-build-go.fetch", "ace", c.callerToken())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if called != "mesh.mod.mesh-build-go.call.fetch.ace.ace~mesh-delivery-builder" {
|
|
t.Errorf("called: %q", called)
|
|
}
|
|
if fallback != "mesh.mod.mesh-build-go.tool.fetch.ace" {
|
|
t.Errorf("fallback: %q", fallback)
|
|
}
|
|
}
|
|
|
|
// 1470/AC2: seat:node-files.read on g14.
|
|
func TestCallSubjectsOnANodeForASeatVerb(t *testing.T) {
|
|
called, fallback, err := callSubjectsOn("seat:node-files.read", "g14", "tok")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if called != "mesh.seat.node-files.call.read.g14.tok" || fallback != "mesh.seat.node-files.tool.read.g14" {
|
|
t.Errorf("called %q, fallback %q", called, fallback)
|
|
}
|
|
// Issue 774 notes 84-87: the machine named in the key after '@' is the same subject, never a token holding '@'.
|
|
called, fallback, err = callSubjects("seat:node-files.read@g14", "tok")
|
|
if err != nil || called != "mesh.seat.node-files.call.read.g14.tok" || fallback != "mesh.seat.node-files.tool.read.g14" {
|
|
t.Errorf("@g14: called %q, fallback %q, %v", called, fallback, err)
|
|
}
|
|
if _, _, err := callSubjectsOn("seat:node-files.read@ace", "g14", "tok"); err == nil {
|
|
t.Error("a key naming ace was asked of g14")
|
|
}
|
|
}
|
|
|
|
// 1470/AC3: a node that is not one token is refused before anything is published (the Conn has no
|
|
// connection, so only the name check can answer with this error).
|
|
func TestCallOnRefusesANodeThatIsNotOneToken(t *testing.T) {
|
|
c := &Conn{}
|
|
for _, node := range []string{"", "a.b", "*", ">", "a b", "a\tb", "a@b"} {
|
|
if _, err := c.CallOn(node, "mesh-build-go.fetch", nil); err == nil || !strings.Contains(err.Error(), "not a node name") {
|
|
t.Errorf("CallOn(%q) = %v, want the refusal of the name", node, err)
|
|
}
|
|
}
|
|
// Issue 774 notes 84-87: Call refuses a malformed key the same way, before anything is published.
|
|
for _, key := range []string{"seat:node-service-manager.stop@novox-be.lan", "seat:a.b@x@y", "seat:a.*", "a.b@"} {
|
|
if _, err := c.Call(key, nil); err == nil {
|
|
t.Errorf("Call(%q) was published", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The review of 2026-10-30 (issue 774): a callee's own answer is delivered, whatever its words, and is never asked
|
|
// again during a blackout; only the transport's no responders is.
|
|
func TestACallIsAskedAgainOnlyWhenItReachedNoOne(t *testing.T) {
|
|
t.Cleanup(busretry.Waits(10*time.Second, time.Millisecond, 2*time.Millisecond))
|
|
for _, row := range []struct {
|
|
name string
|
|
answer []error // per ask; the last repeats
|
|
asks int
|
|
}{
|
|
{"a callee answers jetstream not enabled: asked once", []error{answered("jetstream not enabled")}, 1},
|
|
{"a callee answers no responders in its words: asked once", []error{answered("nats: no responders available for request")}, 1},
|
|
{"no responders on either subject, then an answer", []error{nats.ErrNoResponders, nats.ErrNoResponders, nil}, 3},
|
|
} {
|
|
asks := 0
|
|
_, err := callWith(func(string, []byte) (json.RawMessage, error) {
|
|
e := row.answer[min(asks, len(row.answer)-1)]
|
|
asks++
|
|
return json.RawMessage(`{}`), e
|
|
}, func() bool { return true }, "called", "fallback", nil)
|
|
if asks != row.asks {
|
|
t.Errorf("%s: asked %d times (%v), want %d", row.name, asks, err, row.asks)
|
|
}
|
|
}
|
|
}
|