mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
One commit with exactly the tree of the reviewed head35b4509c0a, on main12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
156 lines
5.5 KiB
Go
156 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
)
|
|
|
|
// A module's code asks at runtime (novox/hq ADR 0349 clause 14, ADR 0370, design 57 S1, issue 685), against a real
|
|
// bus whose accounts are the client's and its machine's tool runner's as mesh-controller composes them: the tool
|
|
// runner asks for a carried module's config value and is answered; a change is said, so a watch asks again; a key
|
|
// the module does not declare, a module not placed on the machine, a secret and a malformed ask are refused, each
|
|
// with its reason.
|
|
|
|
// toolRunner is g14's tool runner on the bus, asking as its account may.
|
|
type toolRunner struct {
|
|
t *testing.T
|
|
nc *nats.Conn
|
|
}
|
|
|
|
func aToolRunner(t *testing.T, b *aBus) *toolRunner {
|
|
t.Helper()
|
|
nc, err := nats.Connect(b.url, nats.UserInfo(node+".node-tools", "x"),
|
|
nats.CustomInboxPrefix("_INBOX."+node+".node-tools"),
|
|
nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
|
|
t.Errorf("the bus refused the tool runner: %v", err)
|
|
}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(nc.Close)
|
|
return &toolRunner{t: t, nc: nc}
|
|
}
|
|
|
|
func (r *toolRunner) ask(body string) Answered {
|
|
r.t.Helper()
|
|
var a Answered
|
|
deadline := time.Now().Add(10 * time.Second)
|
|
for {
|
|
msg, err := r.nc.Request(AskSubject(node), []byte(body), 2*time.Second)
|
|
if err == nil {
|
|
if err := json.Unmarshal(msg.Data, &a); err != nil {
|
|
r.t.Fatalf("the client answered %s", msg.Data)
|
|
}
|
|
return a
|
|
}
|
|
if time.Now().After(deadline) {
|
|
r.t.Fatalf("the client did not answer %s: %v", body, err)
|
|
}
|
|
time.Sleep(50 * time.Millisecond) // not listening yet
|
|
}
|
|
}
|
|
|
|
func (r *toolRunner) askConfig(module, key string) Answered {
|
|
r.t.Helper()
|
|
return r.ask(fmt.Sprintf(`{"module":%q,"kind":"config","key":%q}`, module, key))
|
|
}
|
|
|
|
// hear collects what the client says moved on this machine.
|
|
func (r *toolRunner) hear() chan string {
|
|
r.t.Helper()
|
|
heard := make(chan string, 64)
|
|
if _, err := r.nc.Subscribe("prov.changed."+node+".>", func(m *nats.Msg) {
|
|
heard <- strings.TrimPrefix(m.Subject, "prov.changed."+node+".") + " " + string(m.Data)
|
|
}); err != nil {
|
|
r.t.Fatal(err)
|
|
}
|
|
_ = r.nc.Flush()
|
|
return heard
|
|
}
|
|
|
|
// placed writes the plan of a module placed here with nothing to provision: an empty plan (ADR 0570: a placed
|
|
// module has a plan key, empty or not).
|
|
func (b *aBus) placed(t *testing.T, module string) {
|
|
t.Helper()
|
|
b.put(t, PlansBucket, node+"."+module, fmt.Sprintf(`{"node":%q,"module":%q,"provisioning":{}}`,
|
|
node, module))
|
|
}
|
|
|
|
func until(t *testing.T, heard chan string, want string) {
|
|
t.Helper()
|
|
deadline := time.After(10 * time.Second)
|
|
for {
|
|
select {
|
|
case got := <-heard:
|
|
if got == want {
|
|
return
|
|
}
|
|
case <-deadline:
|
|
t.Fatalf("the client did not say %s", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestNatsAModulesCodeReadsItsConfigAndIsToldOfAChange(t *testing.T) {
|
|
b := startBus(t)
|
|
e := startEngine(t, nil)
|
|
b.placed(t, "claude-code")
|
|
b.config(t, "claude-code", "managed_settings", `{"spinnerTipsEnabled":false}`)
|
|
runner := aToolRunner(t, b)
|
|
heard := runner.hear()
|
|
aClient(t, b, e, t.TempDir())
|
|
|
|
// The value as mesh-config resolved it.
|
|
if a := runner.askConfig("claude-code", "managed_settings"); a.Error != "" || string(a.Value) != `{"spinnerTipsEnabled":false}` {
|
|
t.Fatalf("answered %+v", a)
|
|
}
|
|
// Coming up, it says every module's config moved, so a watch held across its absence asks again.
|
|
until(t, heard, `claude-code {"kind":"config"}`)
|
|
|
|
// A change is said, which value and never the value; asked again, the new value.
|
|
b.config(t, "claude-code", "managed_settings", `{"spinnerTipsEnabled":true}`)
|
|
until(t, heard, `claude-code {"kind":"config","key":"managed_settings"}`)
|
|
if a := runner.askConfig("claude-code", "managed_settings"); string(a.Value) != `{"spinnerTipsEnabled":true}` {
|
|
t.Fatalf("after the change, answered %+v", a)
|
|
}
|
|
|
|
// Taken off the machine: said, and no longer answered.
|
|
b.del(t, PlansBucket, node+".claude-code")
|
|
until(t, heard, `claude-code {"kind":"config"}`)
|
|
if a := runner.askConfig("claude-code", "managed_settings"); !strings.Contains(a.Error, "not placed on g14") {
|
|
t.Fatalf("a module taken off the machine was answered %+v", a)
|
|
}
|
|
}
|
|
|
|
func TestNatsWhatAModulesCodeMayNotReadIsRefusedWithTheReason(t *testing.T) {
|
|
b := startBus(t)
|
|
e := startEngine(t, nil)
|
|
b.placed(t, "claude-code")
|
|
b.config(t, "claude-code", "managed_settings", `{}`)
|
|
// Resolved for a module the composition does not place on this machine.
|
|
b.config(t, "dunst", "font-size", "13")
|
|
runner := aToolRunner(t, b)
|
|
aClient(t, b, e, t.TempDir())
|
|
|
|
for _, c := range []struct{ ask, refused string }{
|
|
{`{"module":"claude-code","kind":"config","key":"no_such_key"}`, "declares no config value no_such_key"},
|
|
{`{"module":"dunst","kind":"config","key":"font-size"}`, "dunst is not placed on g14"},
|
|
{`{"module":"claude-code","kind":"secret","key":"gitea-read"}`, "not answered on the tool runner's word"},
|
|
{`{"module":"claude-code","kind":"bound","key":"mcp-endpoint:url"}`, "not answered yet"},
|
|
{`{"module":"claude-code","kind":"setting","key":"role"}`, "not a kind the client answers"},
|
|
{`{"module":"claude-code","kind":"config"}`, "names its module, its kind and its key"},
|
|
{`{"module":"claude-code","kind":"config","key":"managed_settings","as":"dunst"}`, "names its module, its kind and its key"},
|
|
{`not json`, "names its module, its kind and its key"},
|
|
} {
|
|
a := runner.ask(c.ask)
|
|
if len(a.Value) != 0 || !strings.Contains(a.Error, c.refused) {
|
|
t.Errorf("%s: answered %+v, want refused with %q", c.ask, a, c.refused)
|
|
}
|
|
}
|
|
}
|