Files
mesh-catalog/modules/claude-code/cmd/claude-code/insteadof_test.go
T
jochen f72a435487
mesh/merge-gate pass: builds claude-code → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
claude-code: read where it runs from the controller's JSON answer
nodesRunningMe looked for printed lines in what is a JSON list, so every register tool's "also on"
hint and every nodes: "all" named no machine.
2026-10-07 20:28:06 +02:00

156 lines
7.4 KiB
Go

package main
import (
"encoding/json"
"errors"
"os"
"strings"
"testing"
)
// askingAController answers the controller's verbs as the seat does: `tools` in-process, the rest as the
// command's output and its JSON.
func askingAController(t *testing.T) Ask {
t.Helper()
wrap := func(output string, answer any) json.RawMessage {
raw, _ := json.Marshal(map[string]any{"ok": true, "output": output, "answer": answer})
return raw
}
return func(address string, args any) (json.RawMessage, error) {
switch address {
case "seat:mesh-controller.tools":
return json.RawMessage(`{"seats":[
{"seat":"mesh-controller","scope":"mesh","tools":[{"name":"node","description":"What one machine reported it can do, what it is assigned, and why.","replaces":["hostnamectl","uptime"]},{"name":"nodes","description":"Every machine."}]},
{"seat":"node-service-manager","scope":"node","tools":[
{"name":"status","description":"One unit as the service manager sees it now: its states, whether it starts at boot.","replaces":["systemctl status","systemctl is-active"]},
{"name":"journal","description":"The last lines of one unit's journal.","replaces":["journalctl"]}]}]}`), nil
case "seat:mesh-controller.modules":
return wrap("[...]", []any{map[string]any{"module": "docker", "on": []string{"anchor"},
"replaces": map[string][]string{"docker_logs": {"docker logs"}}}, map[string]any{"module": "zsh", "on": []string{"anchor"}}}), nil
case "seat:mesh-controller.nodes":
return wrap("", []any{map[string]any{"name": "anchor"}, map[string]any{"name": "laptop"}}), nil
case "seat:mesh-controller.node":
node, _ := args.(map[string]any)["node"].(string)
if node == "anchor" {
return wrap("anchor\n mode converged\n public domain Anchor.Example\n\n what it runs\n", nil), nil
}
return wrap("laptop\n mode converged\n", nil), nil
}
return nil, errors.New("no such verb")
}
}
func resolving(name string) []string {
return map[string][]string{"anchor": {"10.10.0.1"}, "anchor.internal": {"10.10.0.1"}, "anchor.example": {"192.0.2.10"},
"laptop": {"127.0.0.1", "10.10.0.4"}}[name]
}
// What the controller says of its tools and machines is read whole: the seats' verbs, the modules' own tools,
// every machine with its domains and addresses — a loopback address never one of them.
func TestTheMeshsToolsAreAskedOfTheController(t *testing.T) {
m, err := AskMeshTools(askingAController(t), resolving)
if err != nil {
t.Fatal(err)
}
var addresses []string
for _, r := range m.Replacements {
addresses = append(addresses, r.Address)
}
if got := strings.Join(addresses, ","); got != "mesh-controller.node,node-service-manager.status,node-service-manager.journal,docker.docker_logs" {
t.Errorf("replacements: %s", got)
}
if len(m.Machines) != 2 || strings.Join(m.Machines[0].Domains, ",") != "anchor.internal,anchor.example" ||
strings.Join(m.Machines[0].Addresses, ",") != "10.10.0.1,192.0.2.10" || strings.Join(m.Machines[1].Addresses, ",") != "10.10.0.4" {
t.Errorf("machines: %+v", m.Machines)
}
if m.Replacements[3].Does != "logs" || !m.Replacements[2].NodeScoped || m.Replacements[0].NodeScoped {
t.Errorf("the rows: %+v", m.Replacements)
}
}
// **The table is generated, not written** (novox/hq ADR 0245): what the records say a verb replaces is a row,
// the machines' seats first, and nothing is rendered where the records say nothing.
func TestTheInsteadOfTableIsGeneratedFromTheRecords(t *testing.T) {
m, _ := AskMeshTools(askingAController(t), resolving)
table := InsteadOf(&m)
for _, want := range []string{
"| `<node>/node-service-manager.` | `status` — `systemctl status`, `systemctl is-active` · `journal` — `journalctl` |",
"| `<node>/docker.` | `docker_logs` — `docker logs` |",
"| `mesh-controller.` | `node` — `hostnamectl`, `uptime` |",
"Never `ssh` to a mesh machine",
} {
if !strings.Contains(table, want) {
t.Errorf("the table lacks %q:\n%s", want, table)
}
}
if strings.Index(table, "node-service-manager.") > strings.Index(table, "docker.") ||
strings.Index(table, "docker.") > strings.Index(table, "`mesh-controller.`") {
t.Errorf("not the machines' seats, then modules, then the mesh's:\n%s", table)
}
// What the guard here named most comes first: its row, and in its row its verb.
m.Refused = map[string]int{"node-service-manager.journal": 3, "mesh-controller.node": 1}
ranked := InsteadOf(&m)
if !strings.Contains(ranked, "| `<node>/node-service-manager.` | `journal` — `journalctl` · `status`") ||
strings.Index(ranked, "`mesh-controller.`") > strings.Index(ranked, "`<node>/docker.`") {
t.Errorf("not ranked by the guard's record:\n%s", ranked)
}
m.Refused = nil
if InsteadOf(nil) != "" || InsteadOf(&MeshTools{}) != "" {
t.Error("a table from nothing")
}
// Into the instructions after the mesh's own text, before what was registered.
out := RenderWithMesh(Facts{Node: "w", Console: "x"}, Settings{}, nil, "/h", nil,
Config{Mesh: []Item{{Kind: KindInstructions, Name: "conventions", Scope: ScopeMesh, Files: map[string]string{"conventions.md": "Be brief."}}}}, &m)
md := out["CLAUDE.md"]
if i, j, k := strings.Index(md, "## Conventions"), strings.Index(md, "## Instead of a shell command"), strings.Index(md, "### conventions"); !(i < j && j < k && i >= 0) {
t.Errorf("the table is not between the mesh's text and the registered sections:\n%s", md)
}
if os.Getenv("SHOW_TABLE") != "" {
t.Log(table)
}
// A long list is cut, and says how the rest is found.
var many MeshTools
for i := 0; i < InsteadOfRows+3; i++ {
many.Replacements = append(many.Replacements, Replacement{Address: "s" + string(rune('a'+i)) + ".v", Seat: true, Replaces: []string{"c"}})
}
if t2 := InsteadOf(&many); strings.Count(t2, "| `s") != InsteadOfRows || !strings.Contains(t2, "3 more seats and modules") {
t.Errorf("not cut to %d rows:\n%s", InsteadOfRows, t2)
}
}
// Asked again with the same answer, nothing is written and nothing renders.
func TestTheMeshsToolsAreKeptOnlyWhenTheyChange(t *testing.T) {
p := Paths{State: t.TempDir()}
m, _ := AskMeshTools(askingAController(t), resolving)
if !KeepMeshTools(p, m) {
t.Fatal("the first answer was not kept")
}
if KeepMeshTools(p, m) {
t.Fatal("the same answer was kept again")
}
m.Machines = m.Machines[:1]
if !KeepMeshTools(p, m) || len(ReadMeshTools(p).Machines) != 1 {
t.Fatal("a changed answer was not kept")
}
d := GuardDataOf(Paths{State: p.State, Node: "laptop"}, ReadMeshTools(p))
if d.Node != "laptop" || len(d.Replacements) != 4 || !strings.HasSuffix(d.Log, "guard.log") {
t.Errorf("the guard's data: %+v", d)
}
}
// Where claude-code runs is read from the controller's `modules` answer as it comes — JSON, from `module list
// --json` — and from the printed list too.
func TestTheMachinesRunningTheModuleAreReadFromTheControllersAnswer(t *testing.T) {
asJSON := json.RawMessage(`{"ok":true,"output":"[...]","answer":[{"module":"zsh","on":["a"]},{"module":"claude-code","on":["a","b"]}]}`)
if got := strings.Join(NodesRunning(asJSON, "claude-code"), ","); got != "a,b" {
t.Errorf("from JSON: %q", got)
}
printed := json.RawMessage(`{"ok":true,"output":"zsh 1 built x on a\nclaude-code 1 built y on a, b\n"}`)
if got := strings.Join(NodesRunning(printed, "claude-code"), ","); got != "a,b" {
t.Errorf("from the printed list: %q", got)
}
if got := NodesRunning(json.RawMessage(`{"ok":true,"answer":[{"module":"zsh","on":["a"]}]}`), "claude-code"); got != nil {
t.Errorf("a module that runs nowhere: %v", got)
}
}