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

217 lines
7.8 KiB
Go

package bootstrap
import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// Step 7 installs the one module whose image can never come from the mesh's own registry, because
// it IS the mesh's own registry (novox/hq 04-ISSUES/029). These tests defend that, and defend the
// distinction the whole verify layer of this program is built on: a container that is up is not a
// service that answers.
// catalogueWith writes a fake catalogue checkout holding one module's manifest.
//
// A fixture here rather than the real catalogue, unlike the foundation example the rewrite tests
// use: the catalogue is a different repository on a different branch, and a test that read it
// would pass or fail according to what somebody else had checked out.
func catalogueWith(t *testing.T, module, manifest string) string {
t.Helper()
root := t.TempDir()
dir := filepath.Join(root, catalogueDir, module)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "module.json"), []byte(manifest), 0o644); err != nil {
t.Fatal(err)
}
return root
}
const upstreamRegistryManifest = `{
"module": "distribution",
"version": "1",
"provides": [{"name": "artifact-store", "scope": "mesh"}],
"capabilities": ["container-runtime"],
"resources": [
{"id": "state", "type": "directory", "path": "/var/lib/mesh/registry", "mode": "0700"},
{"id": "store", "type": "container", "name": "mesh-registry",
"image": "registry@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373",
"ports": ["5000:5000"]}
]
}`
// aMeshThatAgrees answers every command the installer issues at steps 7 and 9 the way a working
// mesh would, except for whatever a test overrides.
func aMeshThatAgrees(answers map[string]string) func(string, []string) (string, error) {
return func(name string, args []string) (string, error) {
joined := strings.Join(args, " ")
for fragment, said := range answers {
if strings.Contains(joined, fragment) {
return said, nil
}
}
switch {
case name != "docker":
return "", fmt.Errorf("unexpected program %q", name)
case args[0] == "cp":
return "", nil
case args[0] == "inspect":
return "true running\n", nil
case args[0] == "exec":
return "", nil
}
return "", fmt.Errorf("unexpected: %v", args)
}
}
func installing(t *testing.T, catalogue string) Options {
t.Helper()
return Options{
Node: "anchor",
Catalogue: catalogue,
Registry: "127.0.0.1:5000",
Timeout: time.Second,
Wait: 0,
}
}
// A container that is up is not a registry that serves. `/v2/` is the registry API's own "yes, I
// am one and I am ready", and the step after this pushes to it — so it is refused here rather than
// discovered inside a `docker push`.
func TestARegistryContainerThatIsUpIsNotARegistryThatServes(t *testing.T) {
previous := answerEvery
answerEvery = time.Millisecond
defer func() { answerEvery = previous }()
runtime := &asked{answer: aMeshThatAgrees(nil)}
deps := Deps{
Run: runtime.run,
Fetch: func(context.Context, string) (int, string, error) {
return http.StatusInternalServerError, "", nil
},
}
_, err := InstallRegistry(context.Background(),
installing(t, catalogueWith(t, RegistryModule, upstreamRegistryManifest)),
deps, controlPlane{container: "temp-mesh-controller", run: runtime.run, timeout: time.Second},
func(string) {})
if err == nil {
t.Fatal("a registry whose container is up and which answers 500 was accepted")
}
for _, wanted := range []string{"/v2/", "Running is not serving"} {
if !strings.Contains(err.Error(), wanted) {
t.Errorf("the refusal does not mention %q:\n%v", wanted, err)
}
}
}
// The whole of step 7, against a mesh that agrees: registered, assigned, pushed, up, and answering.
func TestARegistryThatAnswersIsAccepted(t *testing.T) {
runtime := &asked{answer: aMeshThatAgrees(nil)}
deps := Deps{
Run: runtime.run,
Fetch: func(context.Context, string) (int, string, error) {
return http.StatusOK, "{}", nil
},
}
out, err := InstallRegistry(context.Background(),
installing(t, catalogueWith(t, RegistryModule, upstreamRegistryManifest)),
deps, controlPlane{container: "temp-mesh-controller", run: runtime.run, timeout: time.Second},
func(string) {})
if err != nil {
t.Fatal(err)
}
if out.Container != "mesh-registry" {
t.Errorf("the registry's container is %q", out.Container)
}
if out.Answered != http.StatusOK {
t.Errorf("the registry answered %d", out.Answered)
}
// Registered, assigned and pushed, through the same three commands a person types.
for _, wanted := range []string{
"module add /distribution-module.json",
"assign anchor distribution",
"push anchor",
} {
if !runtime.ran(wanted) {
t.Errorf("the installer never ran %q: %v", wanted, runtime.commands)
}
}
}
// **The registry's image is upstream and it is never built.** A manifest carrying the catalogue's
// placeholder digest would mean somebody had made this module buildable — which is the cycle
// novox/hq 04-ISSUES/029 settled: a module that provides the artifact store cannot be delivered
// through the artifact store.
func TestARegistryManifestThatWantsBuildingIsRefused(t *testing.T) {
wants := strings.Replace(upstreamRegistryManifest,
"registry@sha256:a3d8aaa63ed8681a604f1dea0aa03f100d5895b6a58ace528858a7b332415373",
"mesh-runtime-registry@"+placeholderDigest, 1)
runtime := &asked{answer: aMeshThatAgrees(nil)}
_, err := InstallRegistry(context.Background(),
installing(t, catalogueWith(t, RegistryModule, wants)),
Deps{Run: runtime.run}, controlPlane{container: "temp-mesh-controller", run: runtime.run},
func(string) {})
if err == nil {
t.Fatal("a registry manifest naming an image the mesh would have to build was accepted")
}
if !strings.Contains(err.Error(), "04-ISSUES/029") {
t.Errorf("the refusal does not name the decision it rests on: %v", err)
}
if runtime.ran("module add") {
t.Error("it was registered anyway")
}
}
// A catalogue that is not there is said plainly, with what --catalog is. This is the most likely
// mistake anybody makes at this step and the least interesting to debug.
func TestACatalogueThatIsNotThereIsSaidPlainly(t *testing.T) {
runtime := &asked{answer: aMeshThatAgrees(nil)}
_, err := InstallRegistry(context.Background(),
installing(t, filepath.Join(t.TempDir(), "nowhere")),
Deps{Run: runtime.run}, controlPlane{container: "temp-mesh-controller", run: runtime.run},
func(string) {})
if err == nil {
t.Fatal("a catalogue that does not exist was accepted")
}
if !strings.Contains(err.Error(), "--catalog") {
t.Errorf("the refusal does not say what to fix: %v", err)
}
}
// A refusal from the control plane is repeated verbatim. mesh-controller refuses in paragraphs —
// "nothing provides route, wanted by registry" — and an installer that reported "exit status 1"
// would throw away the only thing a person can act on.
func TestWhatTheMeshRefusedIsRepeated(t *testing.T) {
refusal := "nothing provides \"route\", wanted by registry"
runtime := &asked{answer: func(name string, args []string) (string, error) {
if strings.Contains(strings.Join(args, " "), "push") {
return refusal, fmt.Errorf("exit status 1")
}
return aMeshThatAgrees(nil)(name, args)
}}
_, err := InstallRegistry(context.Background(),
installing(t, catalogueWith(t, RegistryModule, upstreamRegistryManifest)),
Deps{Run: runtime.run}, controlPlane{container: "temp-mesh-controller", run: runtime.run,
timeout: time.Second}, func(string) {})
if err == nil {
t.Fatal("a push the mesh refused was reported as successful")
}
if !strings.Contains(err.Error(), refusal) {
t.Errorf("what the mesh said is not in the failure:\n%v", err)
}
if !strings.Contains(err.Error(), "run this installer again") {
t.Errorf("the failure does not say a re-run continues from here:\n%v", err)
}
}