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", } { 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) } }