Files
jschoubben 121367319d Rename mesh-control -> mesh-controller, substrate -> foundation
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
2026-09-16 18:40:40 +02:00

89 lines
3.6 KiB
Go

package bundle
import (
"errors"
"strings"
"testing"
"github.com/novox/mesh-host/internal/declaration"
)
func TestADefaultBuildCarriesNothingAndSaysSo(t *testing.T) {
// The important one. A host built without a bundle that applied nothing and reported
// success would look exactly like a host that raised a first node — and the difference
// would surface as a mesh that never came up, with nothing to point at.
if !IsEmpty("arch") {
t.Fatal("the default build claims to carry a foundation")
}
_, err := Load("arch")
if !errors.Is(err, ErrEmpty) {
t.Fatalf("an empty bundle did not refuse: %v", err)
}
if !strings.Contains(err.Error(), "would look exactly like applying something") {
t.Errorf("the refusal does not say why it matters: %v", err)
}
}
func TestABundleWithContentIsParsedByTheSameParserTheLinkWillUse(t *testing.T) {
// A bundle that reaches a machine and is then refused by the host carrying it would be a
// build-time mistake found at the worst possible moment.
//
// Comment handling itself is asserted where it now lives, in `declaration`. It was here, and
// having it in two places is how it came to be *done* in two places.
real := []byte(`// pinned
{"declaration":1,"resources":[{"id":"d","type":"directory","path":"/etc/mesh"}]}`)
parsed, err := declaration.ParseFileTrusted(real)
if err != nil {
t.Fatalf("an annotated bundle was refused: %v", err)
}
if len(parsed.Resources) != 1 {
t.Fatalf("got %d resources", len(parsed.Resources))
}
}
func TestWhatValidatesIsWhatIsApplied(t *testing.T) {
// Found on a real machine. `mesh-host bundle` validated the carried bundle through Load,
// which strips comments; `reconcile` handed the RAW bytes to the parser, which does not.
// So the command whose whole job is to check the bundle said yes, and the command that
// uses it said no — two paths to one artefact, disagreeing.
//
// There is now one path. This asserts the property that made the bug possible cannot
// return: whatever Load accepts is what gets applied, byte for byte.
annotated := []byte("// a comment\n" + `{"declaration":1,"resources":[{"id":"d","type":"directory","path":"/etc/mesh"}]}`)
if _, err := declaration.ParseFileTrusted(annotated); err != nil {
t.Fatalf("an annotated bundle was refused: %v", err)
}
}
func TestEverySystemHasABundleAndAndroidsRefuses(t *testing.T) {
// The bundle's contents are per system even though its mechanism is not (novox/hq ADR
// 0060), so a host must find one built for it — and a host built for a system with no
// bundle at all must say that rather than behave like an empty one.
for _, system := range []string{"arch", "alpine", "android"} {
if _, err := Load(system); err == nil {
t.Errorf("%s: a placeholder bundle loaded as if it had contents", system)
} else if !errors.Is(err, ErrEmpty) {
t.Errorf("%s: refused for the wrong reason: %v", system, err)
}
}
if _, err := Load("debian"); err == nil {
t.Error("a bundle was loaded for a system nobody has built")
} else if errors.Is(err, ErrEmpty) {
t.Error("an unbuilt system was reported as an empty bundle; those are different things")
}
}
func TestAndroidsBundleSaysWhyThereIsNone(t *testing.T) {
// Not a placeholder waiting to be filled in. An android host implements neither `package`
// nor `container` nor `service`, so every step of the bootstrap is a shape it does not
// have — a partial host can JOIN a mesh and cannot BE the first node.
text := string(Raw("android"))
for _, want := range []string{"cannot raise a mesh", "JOIN", "first node"} {
if !strings.Contains(text, want) {
t.Errorf("the android bundle does not explain itself; missing %q", want)
}
}
}