Twelve steps made a mesh that RUNS and then said "what remains is somebody else's". The seven things that turn it into a mesh that WORKS — the shared base, a database provider, the catalogue, the private network, the packet filter — were typed afterwards, which is how they went missing for weeks without anything complaining. Six more steps now: base, store, catalogue, network, filter, extras. Everything in them is module add, build, assign and push — the same verbs a person types, through the same commands, so the installer and an operator remain one act. Where a human must choose, the installer asks. A choice resolves in the order a person expects: the flag wins; a lone option answers itself ALOUD, because "it chose for me" and "there was nothing to choose" read identically afterwards unless one speaks; a terminal is asked; a default fills in; and a required choice nothing answered refuses naming its flag — a guessed packet filter is a machine somebody else configured. The filter is required, so the question is which, not whether. A run without a terminal (the lab, --json) is never left waiting on a prompt nobody will answer. Placement is part of the network step, not a separate act — a lesson paid for: the module installed, the names file was written with no names in it, and everything reported success because nobody had said where the machine IS. The hub endpoint derives from the broker address when unsaid: the host other machines dial is one fact, not two that drift. Extras fail the run rather than soft-fail: somebody asked for them by name, and a mesh reporting success minus one thing is reporting the wrong thing. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
167 lines
6.1 KiB
Go
167 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-host/internal/bootstrap"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// Argument handling gets tests for the reason `mesh-host` records: the standard library stops
|
|
// parsing at the first non-flag argument, so a flag sitting after one is silently dropped and the
|
|
// command exits zero having ignored what it was asked. Here that would mean `--dry-run` ignored on
|
|
// a program whose whole job is to change a machine.
|
|
|
|
func TestBootstrapIsWhatItDoesWithNoCommand(t *testing.T) {
|
|
// Running the installer with nothing but flags must install, not print usage: the command is
|
|
// the reason the binary exists, and making somebody type its name twice buys nothing.
|
|
command, opts, _, err := parseArgs([]string{"--dry-run"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if command != "bootstrap" {
|
|
t.Errorf("command = %q, want bootstrap", command)
|
|
}
|
|
if !opts.DryRun {
|
|
t.Error("--dry-run before any subcommand was ignored")
|
|
}
|
|
}
|
|
|
|
func TestFlagsAreReadWhereverTheySit(t *testing.T) {
|
|
for _, args := range [][]string{
|
|
{"bootstrap", "--dry-run", "--bundle", "s.lock", "--json"},
|
|
{"bootstrap", "--json", "--bundle=s.lock", "--dry-run"},
|
|
{"--bundle", "s.lock", "--dry-run", "--json"},
|
|
} {
|
|
_, opts, jsonOut, err := parseArgs(args)
|
|
if err != nil {
|
|
t.Errorf("%v: unexpected error: %v", args, err)
|
|
continue
|
|
}
|
|
if !opts.DryRun || !jsonOut || opts.Template != "s.lock" {
|
|
t.Errorf("%v parsed as dry-run=%v json=%v bundle=%q",
|
|
args, opts.DryRun, jsonOut, opts.Template)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAMistypedFlagIsRefusedNotIgnored(t *testing.T) {
|
|
// Asymmetric cost: an error is a moment's annoyance, and a silently dropped --dry-run is a
|
|
// machine changed by somebody who asked for it not to be.
|
|
if _, _, _, err := parseArgs([]string{"bootstrap", "--dry-runn"}); err == nil {
|
|
t.Fatal("a mistyped flag was accepted")
|
|
}
|
|
}
|
|
|
|
func TestAnUnexpectedArgumentIsRefused(t *testing.T) {
|
|
if _, _, _, err := parseArgs([]string{"bootstrap", "substrate.lock"}); err == nil {
|
|
t.Fatal("a stray argument was ignored rather than refused — the bundle is --bundle")
|
|
}
|
|
}
|
|
|
|
func TestTheDefaultsAreTheDocumentedOnes(t *testing.T) {
|
|
// The usage text is a promise. A default that drifts from what is printed is a small lie that
|
|
// costs somebody an afternoon in front of a machine that will not come up.
|
|
_, opts, jsonOut, err := parseArgs(nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if opts.Template != defaultTemplate {
|
|
t.Errorf("default bundle is %q; the usage text says %q", opts.Template, defaultTemplate)
|
|
}
|
|
if opts.Out != defaultOut {
|
|
t.Errorf("default out is %q; the usage text says %q", opts.Out, defaultOut)
|
|
}
|
|
if opts.State != store.DefaultPath {
|
|
t.Errorf("default state is %q; the host's own default is %q", opts.State, store.DefaultPath)
|
|
}
|
|
if opts.Timeout != 30*time.Second {
|
|
t.Errorf("default timeout is %s; the usage text says 30s", opts.Timeout)
|
|
}
|
|
if opts.Wait != 3*time.Minute {
|
|
t.Errorf("default wait is %s; the usage text says 3m", opts.Wait)
|
|
}
|
|
if opts.DryRun || jsonOut || opts.System != "" {
|
|
t.Error("something is on by default that the usage text describes as a flag")
|
|
}
|
|
}
|
|
|
|
// Every flag the usage text promises must exist, and every flag that exists must be in the usage
|
|
// text. The two drifting apart is how a program acquires a feature nobody can find and a
|
|
// documented option that does nothing.
|
|
func TestTheUsageTextAndTheFlagsAgree(t *testing.T) {
|
|
var opts bootstrap.Options
|
|
var jsonOut bool
|
|
set := newFlagSet(&opts, &jsonOut)
|
|
|
|
declared := map[string]bool{}
|
|
set.VisitAll(func(f *flag.Flag) { declared[f.Name] = true })
|
|
|
|
for name := range declared {
|
|
if !strings.Contains(usage, "--"+name) {
|
|
t.Errorf("--%s exists and the usage text does not mention it", name)
|
|
}
|
|
}
|
|
for _, promised := range []string{
|
|
"bundle", "out", "state", "system", "timeout", "wait", "dry-run", "json",
|
|
"catalog", "node", "registry", "host", "host-service", "host-in-background",
|
|
"tools-source", "tools-ref", "catalog-source", "catalog-ref",
|
|
"private-network", "endpoint", "site", "packet-filter", "extras",
|
|
} {
|
|
if !declared[promised] {
|
|
t.Errorf("the usage text promises --%s and no such flag exists", promised)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The pivot's defaults are the documented ones too, and the one that has no default is the one
|
|
// that decides whether the pivot happens at all.
|
|
func TestThePivotsDefaultsAreTheDocumentedOnes(t *testing.T) {
|
|
_, opts, _, err := parseArgs(nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if opts.Registry != defaultRegistry {
|
|
t.Errorf("default registry is %q; the usage text says %q", opts.Registry, defaultRegistry)
|
|
}
|
|
if opts.Host != defaultHost {
|
|
t.Errorf("default host binary is %q; the usage text says %q", opts.Host, defaultHost)
|
|
}
|
|
if opts.HostService != defaultService {
|
|
t.Errorf("default host service is %q; the usage text says %q",
|
|
opts.HostService, defaultService)
|
|
}
|
|
if opts.HostInBackground {
|
|
t.Error("the host is started unsupervised by default, and no real machine should")
|
|
}
|
|
// **No default, deliberately.** A catalogue this installer went looking for on its own would
|
|
// be a checkout somebody else made, at whatever commit they left it on — and it decides which
|
|
// image the mesh's control plane is pinned to for ever after.
|
|
if opts.Catalogue != "" {
|
|
t.Errorf("--catalog defaults to %q; without one the installer stops at the substrate",
|
|
opts.Catalogue)
|
|
}
|
|
// The machine's own name, because that is what a person already calls it.
|
|
if opts.Node == "" {
|
|
t.Error("no default node name; this machine can say what it is called")
|
|
}
|
|
}
|
|
|
|
// --node overrides the machine's own name rather than being ignored because a default was already
|
|
// computed. The name is what the mesh's records, tokens, assignments and pushes all name.
|
|
func TestTheNodeNameCanBeSaid(t *testing.T) {
|
|
_, opts, _, err := parseArgs([]string{"--node", "anchor", "--catalog", "/somewhere"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if opts.Node != "anchor" {
|
|
t.Errorf("--node anchor parsed as %q", opts.Node)
|
|
}
|
|
if opts.Catalogue != "/somewhere" {
|
|
t.Errorf("--catalog parsed as %q", opts.Catalogue)
|
|
}
|
|
}
|