package main import ( "context" "github.com/novox/mesh-host/internal/link" "github.com/novox/mesh-host/internal/store" "testing" "time" ) // Argument handling gets tests because it already failed silently once: `mesh-host inventory // --json` printed text. The standard library stops parsing at the first non-flag argument, so // the flag sat unread in the positional arguments and the command exited 0 having ignored what // the user asked for. // // Silently doing something other than what was asked, and reporting success, is the fault this // project exists to name — so it gets defended here rather than remembered. func TestAFlagAfterTheCommandIsRead(t *testing.T) { command, opts, err := parseArgs([]string{"inventory", "--json"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if command != "inventory" { t.Errorf("command = %q, want inventory", command) } if !opts.json { t.Error("--json after the subcommand was ignored") } } func TestFlagsAreReadInEitherPosition(t *testing.T) { for _, args := range [][]string{ {"profile", "--json", "--timeout", "3s"}, {"profile", "--timeout=3s", "--json"}, } { _, opts, err := parseArgs(args) if err != nil { t.Fatalf("%v: unexpected error: %v", args, err) } if !opts.json || opts.timeout != 3*time.Second { t.Errorf("%v parsed as json=%v timeout=%s", args, opts.json, opts.timeout) } } } func TestAMistypedFlagIsRefusedNotIgnored(t *testing.T) { // The cost of getting this wrong is asymmetric: an error is a moment's annoyance, and a // silently dropped flag is a report that answers a question nobody asked. if _, _, err := parseArgs([]string{"profile", "--jsom"}); err == nil { t.Fatal("a mistyped flag was accepted") } } func TestAnUnexpectedArgumentIsRefused(t *testing.T) { if _, _, err := parseArgs([]string{"profile", "extra"}); err == nil { t.Fatal("a stray argument was ignored rather than refused") } } func TestTheDefaultsAreTheDocumentedOnes(t *testing.T) { // The usage text promises 10s. A default that drifts from what is printed is a small lie // that costs someone an afternoon. _, opts, err := parseArgs([]string{"profile"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if opts.timeout != 10*time.Second { t.Errorf("default timeout is %s; the usage text says 10s", opts.timeout) } if opts.json { t.Error("json output is on by default; the usage text says it is a flag") } } func TestNoCommandIsNotAnError(t *testing.T) { // Running the binary with no arguments prints usage and exits 0. A host that returns // failure for "tell me what you do" is noise in every script that probes it. command, _, err := parseArgs(nil) if err != nil { t.Fatalf("unexpected error: %v", err) } if command != "" { t.Errorf("command = %q, want empty", command) } } func TestApplyNeedsExactlyOneDeclaration(t *testing.T) { // `apply` takes a file where every other command takes nothing, so the leftover-argument // rule has an exception — and an exception is where a parser stops refusing things it // should. Both directions are checked. if _, _, err := parseArgs([]string{"apply"}); err == nil { t.Error("apply with no file was accepted") } if _, _, err := parseArgs([]string{"apply", "a.json", "b.json"}); err == nil { t.Error("apply with two files was accepted") } command, opts, err := parseArgs([]string{"apply", "decl.json", "--dry-run"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if command != "apply" || opts.file != "decl.json" || !opts.dryRun { t.Errorf("parsed as command=%q file=%q dry-run=%v", command, opts.file, opts.dryRun) } } func TestTheStateHasADocumentedDefault(t *testing.T) { // A host that wrote its state somewhere unexpected would forget what it owns on the next // run, and then leave everything it had applied behind forever. _, opts, err := parseArgs([]string{"owned"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if opts.state != store.DefaultPath { t.Errorf("default state path is %q, not the documented %q", opts.state, store.DefaultPath) } } func TestAFlagAfterAPositionalIsRead(t *testing.T) { // The same fault as TestAFlagAfterTheCommandIsRead, one level down. Taking the subcommand // off the front fixed the flag after the COMMAND and not the flag after its ARGUMENT: the // standard library stops at the first non-flag argument wherever that argument is. for _, args := range [][]string{ {"apply", "decl.json", "--dry-run", "--json"}, {"apply", "--dry-run", "decl.json", "--json"}, {"apply", "--dry-run", "--json", "decl.json"}, } { command, opts, err := parseArgs(args) if err != nil { t.Errorf("%v: unexpected error: %v", args, err) continue } if command != "apply" || opts.file != "decl.json" || !opts.dryRun || !opts.json { t.Errorf("%v parsed as file=%q dry-run=%v json=%v", args, opts.file, opts.dryRun, opts.json) } } } // Defends novox/hq ADR 0100: a reconcile on an adopted node speaks unasked only when what it holds // or its firewall changed — which is how a predecessor still writing is caught, without a report // every five minutes saying nothing new. func TestAReconcileSpeaksOnlyWhenWhatIsHeldChanged(t *testing.T) { w := &adoptionWatch{} held := link.Report{Firewall: "ufw", Held: []link.Held{{ID: "hello-web.page"}, {ID: "hello-web.server"}}} if !w.changed(held) { t.Fatal("the first report of a hold was not said") } again := link.Report{Firewall: "ufw", Held: []link.Held{{ID: "hello-web.server"}, {ID: "hello-web.page"}}} if w.changed(again) { t.Error("the same holds in another order were said again") } rewritten := link.Report{Firewall: "ufw", Held: []link.Held{{ID: "hello-web.page", Changed: "rewritten"}, {ID: "hello-web.server"}}} if !w.changed(rewritten) { t.Error("a held file rewritten by something else was not said") } if !w.changed(link.Report{Firewall: "none", Held: rewritten.Held}) { t.Error("a changed firewall was not said") } } func TestWhatTheLinkPublishedCountsAsSaid(t *testing.T) { w := &adoptionWatch{} report := link.Report{Firewall: "ufw", Held: []link.Held{{ID: "a"}}} applier := w.noting(func(context.Context, []byte, []byte) link.Report { return report }) applier(context.Background(), nil, nil) if w.changed(report) { t.Error("a reconcile repeated what the link had just published") } } func TestAReconcileSpeaksWhenWhatIsReachableChanged(t *testing.T) { // The controller previews a flip from what the node last said is reachable; a port that opened // since must reach it without waiting for the next delivery (novox/hq ADR 0100). w := &adoptionWatch{} before := link.Report{Firewall: "ufw", Reachable: []link.Reach{ {Protocol: "tcp", Address: "0.0.0.0", Port: 22, By: "sshd"}}} if !w.changed(before) { t.Fatal("the first report was not said") } reordered := link.Report{Firewall: "ufw", Reachable: []link.Reach{ {Protocol: "tcp", Address: "0.0.0.0", Port: 22, By: "sshd"}}} if w.changed(reordered) { t.Error("the same reachable set was said again") } opened := link.Report{Firewall: "ufw", Reachable: append(before.Reachable, link.Reach{Protocol: "tcp", Address: "0.0.0.0", Port: 8080, By: "hello-web", Published: true, ContainerPort: 80})} if !w.changed(opened) { t.Error("a newly published port was not said") } }