Files
mesh-tools/node-tools/internal/console/search_test.go
T
jochen aa209313c2
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: its merge-check.sh passed
mesh/delivery delivered
Rank a search the same whatever order discovery heard things in
A tie went to whichever tool discovery listed first, so docker logs found docker_secrets_in_logs
before docker_logs on some runs. A name the query says whole wins, then the shorter name.
2026-10-07 20:27:26 +02:00

152 lines
7.3 KiB
Go

package console
import (
"encoding/json"
"strings"
"testing"
"github.com/novox/mesh-tools/node-tools/internal/announce"
)
// meshToSearch is a mesh as discovery and the controller's records describe it: the service manager's
// verbs on two machines, the controller's own, a docker module with its tools, and what each replaces
// as the records say it — the seats' from `tools`, the module's from `modules`.
func meshToSearch(t *testing.T) *index {
t.Helper()
var endpoints []announce.Endpoint
verbs := map[string]string{
"units": "The units the service manager knows in a scope, each with its load, active and sub state.",
"status": "One unit as the service manager sees it now: its states, whether it starts at boot.",
"restart": "Restart one unit.",
"journal": "The last lines of one unit's journal (at most 2000).",
}
for _, node := range []string{"anchor", "laptop"} {
for verb, does := range verbs {
endpoints = append(endpoints, announce.Endpoint{Kind: announce.KindSeat, Module: "systemd", Seat: "node-service-manager",
Tool: verb, Scope: "node", Node: node, Description: does,
Subject: "mesh.seat.node-service-manager.tool." + verb + "." + node})
}
}
for verb, does := range map[string]string{
"node": "What one machine reported it can do, what it is assigned, and why.",
"nodes": "Every machine the mesh knows, with whether it is converged or adopted.",
} {
endpoints = append(endpoints, announce.Endpoint{Kind: announce.KindSeat, Module: "mesh-controller", Seat: "mesh-controller",
Tool: verb, Scope: "mesh", Description: does, Subject: "mesh.seat.mesh-controller.tool." + verb})
}
for tool, does := range map[string]string{
"docker_list": "Every container on this machine: name, image, state, ports.",
"docker_logs": "The last lines a container wrote.",
"docker_secrets_in_logs": "Whether a container's logs hold a secret.",
"docker_restart": "Restart one container.",
} {
endpoints = append(endpoints, announce.Endpoint{Kind: announce.KindTool, Module: "docker", Tool: tool, Node: "anchor",
Description: does, Subject: "mesh.mod.docker.tool." + tool + ".anchor"})
}
x, _, _ := indexOfEndpoints(endpoints)
tools := json.RawMessage(`{"seats":[{"seat":"node-service-manager","scope":"node","tools":[
{"name":"units","replaces":["systemctl list-units","systemctl --failed"]},
{"name":"status","replaces":["systemctl status","systemctl is-active"]},
{"name":"restart","replaces":["systemctl restart"]},
{"name":"journal","replaces":["journalctl"]}]},
{"seat":"mesh-controller","scope":"mesh","tools":[{"name":"node","replaces":["hostnamectl","uptime"]},{"name":"nodes"}]}]}`)
x.Replaces = replacesOf(tools, []recordedModule{{Module: "docker", On: []string{"anchor"}, Tools: true,
Replaces: map[string][]string{"docker_list": {"docker ps"}, "docker_logs": {"docker logs"}, "docker_restart": {"docker restart"}}}})
return x
}
// **The words an agent has find the verb first** (novox/hq ADR 0245): the command it would have run, or
// the word it would have used, puts the mesh's tool for it at the top.
func TestSearchFindsTheToolThatReplacesACommandFirst(t *testing.T) {
x := meshToSearch(t)
for query, want := range map[string]string{
"journalctl": "anchor/node-service-manager.journal",
"journalctl -u mesh-controller -n 50": "anchor/node-service-manager.journal",
"sudo journalctl -fu sshd": "anchor/node-service-manager.journal",
"/usr/bin/journalctl": "anchor/node-service-manager.journal",
"logs": "anchor/node-service-manager.journal",
"systemctl status": "anchor/node-service-manager.status",
"systemctl status nats.service": "anchor/node-service-manager.status",
"systemctl restart mesh-controller": "anchor/node-service-manager.restart",
"systemctl --failed": "anchor/node-service-manager.units",
"docker ps": "anchor/docker.docker_list",
"docker ps -a": "anchor/docker.docker_list",
"docker logs": "anchor/docker.docker_logs",
"docker logs --tail 100 nats": "anchor/docker.docker_logs",
"docker restart nats": "anchor/docker.docker_restart",
"container logs": "anchor/docker.docker_logs",
"uptime": "mesh-controller.node",
} {
hits := x.search(query)
if len(hits) == 0 {
t.Errorf("%q found nothing; want %s first", query, want)
continue
}
if hits[0].Address != want {
var got []string
for _, h := range hits {
got = append(got, h.Address)
}
t.Errorf("%q found %s first; want %s (all: %s)", query, hits[0].Address, want, strings.Join(got, ", "))
}
}
}
// A match on a replaced command says which command it replaced, and the most specific one wins.
func TestASearchByCommandSaysWhatItReplaces(t *testing.T) {
x := meshToSearch(t)
hits := x.search("systemctl status sshd")
if hits[0].InsteadOf != "systemctl status" {
t.Errorf("the match does not say what it replaces: %+v", hits[0])
}
if hits[0].Also != "also on laptop" {
t.Errorf("a node seat's verb does not say its other machines: %+v", hits[0])
}
// `logs` also finds the container's logs, after the journal — both are answers.
var docker bool
for _, h := range x.search("logs")[:3] {
docker = docker || h.Address == "anchor/docker.docker_logs"
}
if !docker {
t.Errorf("`logs` does not find docker_logs among the first: %+v", x.search("logs"))
}
}
// What search did before stays: every word in a name or description, and nothing for a word nothing has.
// A synonym is a word, never part of one: `logs` matches through `log`, and finds no `login`.
func TestSearchStillMatchesEveryWord(t *testing.T) {
x := meshToSearch(t)
if hits := x.search("machine converged"); len(hits) != 1 || hits[0].Address != "mesh-controller.nodes" {
t.Errorf("words in a description: %+v", hits)
}
if hits := x.search("journalctl kubernetes"); len(hits) != 1 || hits[0].Address != "anchor/node-service-manager.journal" {
t.Errorf("a command with arguments nothing has is still that command: %+v", hits)
}
if hits := x.search("kubernetes"); len(hits) != 0 {
t.Errorf("a word nothing has matched: %+v", hits)
}
x.Modules["shell"] = &moduleInfo{Module: "shell", On: []string{"anchor"}, Tools: []Tool{{Module: "shell", Name: "shell_login",
Description: "The account's login shell."}}}
for _, h := range x.search("logs") {
if h.Address == "anchor/shell.shell_login" {
t.Errorf("`logs` found `login` through its synonym `log`: %+v", h)
}
}
}
// A controller older than `replaces` answers without it: search matches names and descriptions as before.
func TestSearchWithoutReplacesMatchesAsBefore(t *testing.T) {
x := meshToSearch(t)
x.Replaces = replacesOf(json.RawMessage(`{"seats":[{"seat":"node-service-manager","tools":[{"name":"journal"}]}]}`),
[]recordedModule{{Module: "docker"}})
if len(x.Replaces) != 0 {
t.Fatalf("replaces from records that have none: %v", x.Replaces)
}
if hits := x.search("journal"); len(hits) == 0 || hits[0].Address != "anchor/node-service-manager.journal" {
t.Errorf("a verb's own name: %+v", hits)
}
if hits := x.search("docker logs"); len(hits) == 0 || hits[0].Address != "anchor/docker.docker_logs" {
t.Errorf("a tool's own name: %+v", hits)
}
}