Files
mesh-controller/cmd/mesh-control/status_test.go
T
jschoubben 0be227bd7e status: one machine that cannot be worked out no longer takes the answer from the rest
`status --json` emitted no JSON at all when a single node was unresolvable. A blocked
node is not on the private network, and a mesh whose hub is that node has no hub — which
came back through the reading as a refusal, so `status` printed nothing and `status
--json` put multi-line prose on stderr and not one byte on stdout. A machine-readable
interface that stops being machine-readable exactly when something is wrong is one nobody
can build an alarm on.

Why a machine cannot be worked out is read as data now, per machine, through the same
whoResolves the private network is built from — so this and the network agree about who
could not be resolved rather than deciding it twice. The private network failing to
compute is kept as a note beside it instead of ending the read: it is almost always a
consequence of those same refusals, and every question that does not depend on it is
still answered.

It reaches all three ways of saying it, from the one reading: the text form leads with it
because a machine here is in none of the answers below, the JSON carries `unresolved`
(always a list, never null) and `network`, and the page has a section of its own.

That also closes a silent success. A machine that resolves to nothing has nothing
computed for it, so there is nothing to compare it against and nothing it can be behind —
it appeared in no answer at all, and `status` reported a mesh where nothing could be sent
anywhere as "all doing what they were told".

statusAsJSON takes the whole reading now rather than a growing argument list, which is
what let an answer be added to the text form and forgotten here. The two are one
function's output in two shapes and must not be able to differ about what was asked.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-10 21:12:06 +02:00

122 lines
3.8 KiB
Go

package main
import (
"encoding/json"
"strings"
"testing"
)
// **One machine's failure must never take the answer away from a reader asking about the others.**
//
// A blocked machine is not on the private network, and a mesh whose hub is that machine has no hub
// — which came back through the reading as a refusal, so `status` printed nothing at all and
// `status --json` emitted multi-line prose on stderr and not one byte of JSON. A machine-readable
// interface that stops being machine-readable exactly when something is wrong is one nobody can
// build an alarm on.
func TestOneBlockedMachineDoesNotDestroyTheWholeDocument(t *testing.T) {
open := aMesh(t)
ctx := t.Context()
// Break the hub, using nothing but the command a person has.
one, two := rivals()
register(t, open, one)
register(t, open, two)
for _, m := range []string{"rival-one", "rival-two"} {
if _, err := assign(ctx, open, "anchor", m); err != nil && m == "rival-one" {
t.Fatal(err)
}
}
asked, err := theThreeQuestions(ctx, open)
if err != nil {
t.Fatalf("one blocked machine made the whole mesh unreadable: %v", err)
}
body, err := statusAsJSON(asked)
if err != nil {
t.Fatal(err)
}
var parsed map[string]any
if err := json.Unmarshal(body, &parsed); err != nil {
t.Fatalf("what a script would read is not JSON: %v\n%s", err, body)
}
// The failure is in the document, as data, on the machine it belongs to.
unresolved, _ := parsed["unresolved"].([]any)
if len(unresolved) == 0 {
t.Fatalf("a machine that cannot be worked out is absent from the document:\n%s", body)
}
var found bool
for _, row := range unresolved {
entry, _ := row.(map[string]any)
if entry["node"] != "anchor" {
continue
}
found = true
problem, _ := entry["problem"].(string)
if !strings.Contains(problem, "the-seat") {
t.Errorf("the machine's problem is not its own words: %q", problem)
}
}
if !found {
t.Fatalf("the blocked machine is not the one named:\n%s", body)
}
// And the mesh is still counted, so a reader can tell "none blocked" from "none at all".
if parsed["machines"] != float64(2) {
t.Errorf("the rest of the document did not survive: %v", parsed["machines"])
}
}
// A well mesh carries the field as an empty list, not as nothing: a reader distinguishing "none
// blocked" from "this field is missing" would have to handle both, and null is the one that gets
// forgotten.
func TestAWellMeshCarriesAnEmptyUnresolvedList(t *testing.T) {
open := aMesh(t)
asked, err := theThreeQuestions(t.Context(), open)
if err != nil {
t.Fatal(err)
}
body, err := statusAsJSON(asked)
if err != nil {
t.Fatal(err)
}
var parsed map[string]any
if err := json.Unmarshal(body, &parsed); err != nil {
t.Fatal(err)
}
list, ok := parsed["unresolved"].([]any)
if !ok {
t.Fatalf("\"unresolved\" is %T, not a list:\n%s", parsed["unresolved"], body)
}
if len(list) != 0 {
t.Fatalf("a well mesh reports blocked machines: %v", list)
}
if _, said := parsed["network"]; said {
t.Errorf("a well mesh says the network could not be computed: %v", parsed["network"])
}
}
// And the page says it too, from the same reading. A board that renders "all well" over a mesh
// where nothing can be sent anywhere is worse than a board that is down.
func TestThePageSaysWhichMachinesCannotBeWorkedOut(t *testing.T) {
open := aMesh(t)
ctx := t.Context()
one, two := rivals()
register(t, open, one)
register(t, open, two)
for _, m := range []string{"rival-one", "rival-two"} {
if _, err := assign(ctx, open, "anchor", m); err != nil && m == "rival-one" {
t.Fatal(err)
}
}
asked, err := theThreeQuestions(ctx, open)
if err != nil {
t.Fatal(err)
}
rendered := render(t, viewOf(asked))
for _, want := range []string{"Can everything be worked out?", "anchor", "the-seat"} {
if !strings.Contains(rendered, want) {
t.Fatalf("the page does not say %q:\n%s", want, rendered)
}
}
}