Files
mesh-host/internal/bootstrap/retire_test.go
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

166 lines
5.8 KiB
Go

package bootstrap
import (
"context"
"errors"
"strings"
"testing"
"time"
"github.com/novox/mesh-host/internal/declaration"
)
// Retirement is destruction by omission, which is the host's ordinary behaviour: it owns what it
// applied and removes what it owns and is no longer declared. These tests defend the bundle
// surgery that expresses it, because a bundle that came out of it unparseable would be found by
// the apply — after the file on the machine had already been replaced.
func produced(t *testing.T) []byte {
t.Helper()
out, err := Rewrite(theRealBundle(t), held)
if err != nil {
t.Fatal(err)
}
return out.Bundle
}
// The temporary control plane leaves the bundle, everything else stays, and what is left parses.
func TestTheTemporaryControlPlaneLeavesTheBundleAndNothingElseDoes(t *testing.T) {
before, err := declaration.ParseFileTrusted(produced(t))
if err != nil {
t.Fatal(err)
}
shorter, err := removeResource(produced(t), ControlPlaneID)
if err != nil {
t.Fatal(err)
}
after, err := declaration.ParseFileTrusted(shorter)
if err != nil {
t.Fatalf("the bundle without the control plane does not parse: %v\n%s", err, shorter)
}
if len(after.Resources) != len(before.Resources)-1 {
t.Fatalf("the bundle went from %d resources to %d, and one was removed",
len(before.Resources), len(after.Resources))
}
if _, err := controlPlaneIn(after); err == nil {
t.Error("the bundle still declares a control plane")
}
// The store and the broker are still exactly what they were. A retirement that took the
// foundation with it would leave the machine with a module and nothing under it.
for id, name := range containerNames(before) {
if id == ControlPlaneID {
continue
}
if containerNames(after)[id] != name {
t.Errorf("%s was lost or renamed by the retirement", id)
}
}
}
// **A `//` inside a string is not a comment.** The foundation's own bundle carries
// `postgres://…` several times, and a scanner that read the rest of those lines as a comment
// would lose braces and cut the wrong thing out — silently, because what it produced would still
// look like a file.
func TestASchemeInsideAStringIsNotReadAsAComment(t *testing.T) {
shorter, err := removeResource(produced(t), ControlPlaneID)
if err != nil {
t.Fatal(err)
}
after, err := declaration.ParseFileTrusted(shorter)
if err != nil {
t.Fatal(err)
}
// The migration action, which is the resource holding the most `://` of anything here, is
// still whole.
found := false
for _, r := range after.Resources {
if r.Identity() == "context-schemas" {
found = true
}
}
if !found {
t.Error("the resource full of connection strings did not survive the removal")
}
}
// Removing the FIRST element takes the comma after it rather than the comma before it, because
// there is no comma before it. Getting this wrong produces a leading comma, which is JSON nothing
// parses — and the file would already have been written.
func TestRemovingTheFirstResourceLeavesAValidList(t *testing.T) {
shorter, err := removeResource(produced(t), "container-runtime")
if err != nil {
t.Fatal(err)
}
after, err := declaration.ParseFileTrusted(shorter)
if err != nil {
t.Fatalf("removing the first resource broke the bundle: %v\n%s", err, shorter)
}
for _, r := range after.Resources {
if r.Identity() == "container-runtime" {
t.Error("the first resource is still there")
}
}
}
// A bundle that declares no such resource is refused rather than silently returned unchanged. A
// removal that removed nothing and reported success would leave the apply below with nothing to
// do and the installer claiming a pivot it did not finish.
func TestRemovingSomethingThatIsNotThereIsRefused(t *testing.T) {
if _, err := removeResource(produced(t), "nothing-of-the-sort"); err == nil {
t.Fatal("a bundle was reported to have had a resource removed that it never declared")
}
}
// A re-run after the retirement finds a bundle with no control plane in it and says so, rather
// than failing. This is the idempotence of the last step, and it is the one a person is most
// likely to exercise: the pivot ends here, so a re-run to check ends here too.
func TestRetiringABundleThatAlreadyHasNoControlPlaneIsAlreadyDone(t *testing.T) {
shorter, err := removeResource(produced(t), ControlPlaneID)
if err != nil {
t.Fatal(err)
}
var said []string
out, err := RetireTheTemporaryControlPlane(context.Background(), Options{},
nil, shorter, nil, func(line string) { said = append(said, line) })
if err != nil {
t.Fatal(err)
}
if !out.Already {
t.Error("a bundle with no control plane in it was not reported as already retired")
}
if !strings.Contains(strings.Join(said, "\n"), "already dropped") {
t.Errorf("the run does not say it was already done: %v", said)
}
}
// A container the runtime still knows about after the apply is a pivot that did not finish. Two
// control planes on one mesh are both correct and neither is wrong — but the temporary one was
// supposed to go, and saying it went when it did not is the fault this project keeps naming.
func TestAContainerStillThereAfterRemovalIsNotGone(t *testing.T) {
previous := answerEvery
answerEvery = time.Millisecond
defer func() { answerEvery = previous }()
stillThere := &asked{answer: func(_ string, _ []string) (string, error) {
return "true running\n", nil
}}
gone, err := isGone(context.Background(), stillThere.run, time.Second, 0, "temp-mesh-controller")
if err != nil {
t.Fatal(err)
}
if gone {
t.Error("a container the runtime still describes was reported gone")
}
removed := &asked{answer: func(_ string, _ []string) (string, error) {
return "", errors.New("No such object: temp-mesh-controller")
}}
gone, err = isGone(context.Background(), removed.run, time.Second, 0, "temp-mesh-controller")
if err != nil {
t.Fatal(err)
}
if !gone {
t.Error("a container the runtime does not know about was not reported gone")
}
}