One name per thing, per the HQ glossary: the module/container/image/binary/repo becomes mesh-controller, the seat the-controller, and the store+broker pair the foundation (embedded base bundles, default template and example lock renamed with their go:embed directives). No behaviour change — a pure vocabulary rename. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
122 lines
3.8 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|