The file on PATH was the engine a person copied at installation, and every delivered engine ran from the versions directory: the operator typed hand-over and a build from before it answered "unexpected argument". A proved engine now copies itself over that file when the bytes differ (never a link). And the hand-over of a directory used as found (issue 339) gets its channel: the controller's terminal asks this node's engine on mesh.node.<node>.ask.hand-over; the engine judges every value — the ask's size and shape, who asked, the path as it states it, a directory and no link, through no link — and records it or refuses and records nothing. Every text the engine addresses to the operator names the nox line (ADR 0272); the engine's own subcommands are install-time and rescue, and its usage says so.
90 lines
3.4 KiB
Go
90 lines
3.4 KiB
Go
package link
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The hand-over's ask and answer hold these field names; the controller's side holds the same list
|
|
// (mesh-controller internal/link, TestTheHandOverAskAndAnswerKeepTheirFieldNames).
|
|
func TestTheHandOverAskAndAnswerKeepTheirFieldNames(t *testing.T) {
|
|
body, _ := json.Marshal(HandOverAsk{Path: "/srv/notes", By: "jo through mesh-cli on anchor"})
|
|
if got := keysOf(t, body); got != "by path" {
|
|
t.Fatalf("the ask's fields are %q", got)
|
|
}
|
|
body, _ = json.Marshal(HandOverAnswer{Said: "s", Refused: "r"})
|
|
if got := keysOf(t, body); got != "refused said" {
|
|
t.Fatalf("the answer's fields are %q", got)
|
|
}
|
|
if AskHandOverSubject("laptop") != "mesh.node.laptop.ask.hand-over" {
|
|
t.Fatalf("the subject is %q", AskHandOverSubject("laptop"))
|
|
}
|
|
}
|
|
|
|
func keysOf(t *testing.T, body []byte) string {
|
|
t.Helper()
|
|
var m map[string]any
|
|
if err := json.Unmarshal(body, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var keys []string
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
return strings.Join(keys, " ")
|
|
}
|
|
|
|
// Every value of an ask is judged before the handler sees it, and a refusal hands nothing to the handler:
|
|
// an ask too large, not a hand-over, naming nobody, naming somebody over more than a line, or naming no
|
|
// directory. What passes reaches the handler with its path as given and who asked trimmed.
|
|
func TestAHandOverAskIsJudgedBeforeAnythingIsRecorded(t *testing.T) {
|
|
var given []string
|
|
handle := func(path, by string) (string, error) {
|
|
given = append(given, path+" by "+by)
|
|
if path == "/refused" {
|
|
return "", errors.New("not a directory this machine uses as found")
|
|
}
|
|
return path + " is handed to the mesh", nil
|
|
}
|
|
refusals := map[string][]byte{
|
|
"too large": []byte(`{"path":"/` + strings.Repeat("a", MaxHandOverAsk) + `","by":"jo"}`),
|
|
"not a hand-over": []byte(`not json`),
|
|
"nobody": []byte(`{"path":"/srv/notes","by":" "}`),
|
|
"two lines": []byte(`{"path":"/srv/notes","by":"jo\nroot"}`),
|
|
"a name too long": []byte(`{"path":"/srv/notes","by":"` + strings.Repeat("x", MaxHandOverBy+1) + `"}`),
|
|
"no directory": []byte(`{"path":"","by":"jo"}`),
|
|
"a NUL in the name": []byte(`{"path":"/srv/notes","by":"jo\u0000"}`),
|
|
}
|
|
for name, body := range refusals {
|
|
a := AnswerHandOver(handle, body)
|
|
if a.Refused == "" || a.Said != "" {
|
|
t.Fatalf("%s was answered %+v; want a refusal", name, a)
|
|
}
|
|
if !strings.Contains(a.Refused, "nothing was handed over") {
|
|
t.Fatalf("%s's refusal does not say nothing was handed over: %q", name, a.Refused)
|
|
}
|
|
}
|
|
if len(given) != 0 {
|
|
t.Fatalf("the handler was given %v although every ask was refused", given)
|
|
}
|
|
if a := AnswerHandOver(nil, []byte(`{"path":"/srv/notes","by":"jo"}`)); a.Refused == "" {
|
|
t.Fatal("an engine with no handler took a hand-over")
|
|
}
|
|
|
|
a := AnswerHandOver(handle, []byte(`{"path":"/refused","by":"jo"}`))
|
|
if a.Refused != "not a directory this machine uses as found" || a.Said != "" {
|
|
t.Fatalf("the handler's refusal was answered as %+v", a)
|
|
}
|
|
a = AnswerHandOver(handle, []byte(`{"path":"/srv/notes","by":" jo through mesh-cli on anchor "}`))
|
|
if a.Said != "/srv/notes is handed to the mesh" || a.Refused != "" {
|
|
t.Fatalf("answered %+v", a)
|
|
}
|
|
if strings.Join(given, "; ") != "/refused by jo; /srv/notes by jo through mesh-cli on anchor" {
|
|
t.Fatalf("the handler was given %v", given)
|
|
}
|
|
}
|