package main import ( "bytes" "errors" "os/exec" "strings" "testing" "time" ) func TestSystemVerbAcceptsTheComponentForm(t *testing.T) { cases := []struct { in []string verb, comp string rest int }{ {[]string{"update", "--channel", "canary"}, "update", "router", 2}, {[]string{"router", "update"}, "update", "router", 0}, {[]string{"router", "status"}, "status", "router", 0}, {[]string{"apply", "--recover"}, "apply", "router", 1}, // The system units' floor gate: both file paths stay in rest. {[]string{"floor-allows-prev", "/data/.moves/floor.min-release", "/opt/carlos/carlos.prev.allowed"}, "floor-allows-prev", "router", 2}, // antivirus is a family: its sub-verb stays in rest untouched. {[]string{"antivirus", "status"}, "antivirus", "router", 1}, {[]string{"antivirus", "enable", "--console", "x"}, "antivirus", "router", 3}, {[]string{"antivirus"}, "antivirus", "router", 0}, {[]string{"files", "seed", "--host", "b.example"}, "files", "router", 3}, // layout is a family too: its sub-verb and flags stay in rest. {[]string{"layout", "migrate", "--box", "x"}, "layout", "router", 3}, {[]string{"layout", "revert", "--force", "--yes"}, "layout", "router", 3}, {[]string{"layout", "status"}, "layout", "router", 1}, // claims is a family: its command and flags stay in rest. {[]string{"claims", "inspect", "--bucket", "b"}, "claims", "router", 3}, // Region HA joining (spec 2 §5): host-env is a family; join and // install-channel take no arguments. {[]string{"host-env", "write", "--deployment", "flagship", "--region", "ie"}, "host-env", "router", 5}, {[]string{"install-channel"}, "install-channel", "router", 0}, {[]string{"join"}, "join", "router", 0}, {[]string{}, "", "router", 0}, {[]string{"router"}, "", "router", 0}, } for _, c := range cases { verb, rest, comp := systemVerb(c.in) if verb != c.verb || comp != c.comp || len(rest) != c.rest { t.Errorf("systemVerb(%v) = %q %v %q", c.in, verb, rest, comp) } } if verb, _, _ := systemVerb([]string{"console", "update"}); verb != "" { t.Errorf("an unknown component must not be a verb: got %q", verb) } } // TestSystemUnknownVerbMessageNamesTheVerbNotTheComponent holds the one thing // the message must never do: report `carlos system router console` as an // unknown verb "router". systemVerb has already eaten the component by then, // so the message has to read what is LEFT, not what arrived. func TestSystemUnknownVerbMessageNamesTheVerbNotTheComponent(t *testing.T) { cases := []struct { in []string want string }{ {[]string{"console"}, `system: unknown verb "console"`}, {[]string{"router", "console"}, `system: unknown verb "console"`}, {[]string{"router"}, "system: no verb given"}, {[]string{}, ""}, } for _, c := range cases { _, rest, _ := systemVerb(c.in) if got := systemUnknownVerbMessage(c.in, rest); got != c.want { t.Errorf("carlos system %v: message = %q, want %q", c.in, got, c.want) } } } func TestPrintSystemStatusSaysWhatEachBoxIsDoing(t *testing.T) { resp := systemStatusResponse{ Pointers: map[string]*systemPointerRow{"canary": {Version: "v2", Spread: "5m0s"}, "stable": nil}, Boxes: []systemBoxRow{ {Label: "ie", Platform: "linux-arm64", Channel: "canary", Release: "v2", Target: "v2"}, {Label: "us", Platform: "linux-arm64", Channel: "canary", Release: "v1", Target: "v2", System: &systemStateRow{Version: "v2", Outcome: "rolled-back", Reason: "gate: carlos-edge is failed"}}, {Label: "au", Platform: "", Channel: "stable", Release: "v1", Target: "", Stale: true}, // Two separate facts, so two separate boxes: "au" is stale and // nothing it last said can be trusted, including its blank // platform; "old" is answering, on a build that predates the // platform field. A box that is both must read as UNREACHABLE. {Label: "old", Platform: "", Channel: "stable", Release: "v0", Target: "v1"}, }, } var b strings.Builder printSystemStatus(&b, resp, "") out := b.String() for _, want := range []string{"canary v2", "stable (no pointer)", "ie ", "current", "us ", "rolled-back", "gate: carlos-edge is failed", "au ", "UNREACHABLE", "pre-upgrade"} { if !strings.Contains(out, want) { t.Errorf("status output lacks %q:\n%s", want, out) } } var only strings.Builder printSystemStatus(&only, resp, "stable") if strings.Contains(only.String(), "ie ") { t.Error("--channel stable still listed a canary box") } } // TestPrintSystemStatusShowsWhatItCouldNotRead is the other half: a pointer // this console cannot verify and a fleet it could not reach are the two // answers an operator must never read as "fine". Neither may render as the // thing it is not — a bad pointer as "never promoted", a short box list as // the whole fleet. func TestPrintSystemStatusShowsWhatItCouldNotRead(t *testing.T) { resp := systemStatusResponse{ Pointers: map[string]*systemPointerRow{ "canary": {Error: "signature does not verify"}, "stable": {Version: "v1", Spread: "5m0s"}, }, Boxes: []systemBoxRow{{Label: "ie", Platform: "linux-arm64", Channel: "stable", Release: "v1", Target: "v1"}}, Partial: true, } var b strings.Builder printSystemStatus(&b, resp, "") out := b.String() for _, want := range []string{"canary (pointer does not verify: signature does not verify)", "fleet boxes could not all be read"} { if !strings.Contains(out, want) { t.Errorf("status output lacks %q:\n%s", want, out) } } if strings.Contains(out, "no pointer") { t.Errorf("an unverifiable pointer must not read as an absent one:\n%s", out) } var quiet strings.Builder printSystemStatus(&quiet, systemStatusResponse{Boxes: resp.Boxes}, "") if strings.Contains(quiet.String(), "could not all be read") { t.Errorf("a complete fleet warned anyway:\n%s", quiet.String()) } } func TestSystemLadderWarning(t *testing.T) { onCanary := map[string]*systemPointerRow{"canary": {Version: "v9"}} never := map[string]*systemPointerRow{} older := map[string]*systemPointerRow{"canary": {Version: "v8"}} const want = "warning: v9 has not been on canary; promoting straight to stable" for _, c := range []struct { name string pointers map[string]*systemPointerRow channel, version string warn string }{ {"the version soaked on canary", onCanary, "stable", "v9", ""}, {"canary has never been promoted", never, "stable", "v9", want}, {"canary is on a different version", older, "stable", "v9", want}, // Promoting TO canary is the rung below; nothing to skip. {"promoting to canary", never, "canary", "v9", ""}, // No --version: the tag is "the latest public release", which only // the server resolves, so there is nothing to compare and a warning // would be a guess. {"the latest public release", never, "stable", "", ""}, } { t.Run(c.name, func(t *testing.T) { if got := systemLadderWarning(c.pointers, c.channel, c.version); got != c.warn { t.Errorf("systemLadderWarning = %q, want %q", got, c.warn) } }) } // A nil map is what an unreadable status body leaves behind: it warns // rather than panicking. if got := systemLadderWarning(nil, "stable", "v9"); got != want { t.Errorf("nil pointers = %q, want %q", got, want) } } // Wiring and policy are two facts, and the output keeps them apart. An // operator debugging "nothing is scanning" needs to know which half is // missing; one "healthy" line would send half of them to the wrong place. func TestPrintSystemAntivirusSeparatesWiringFromPolicy(t *testing.T) { fresh := "ClamAV 1.4.1/27500/" + time.Now().Format(time.ANSIC) // Not wired at all. var b bytes.Buffer printSystemAntivirus(&b, apiSystemAntivirusReply{}) if out := b.String(); !strings.Contains(out, "no engine is wired") || !strings.Contains(out, "CARLOS_CLAMD_ADDR") { t.Fatalf("unwired output does not name the wiring:\n%s", out) } // Wired, switched off. Names the COMMAND, not the variable — this half is // restartless and an operator sent to a unit file would be sent wrong. var b2 bytes.Buffer printSystemAntivirus(&b2, apiSystemAntivirusReply{ Wired: true, EngineWired: "clamav", Signature: fresh, Enabled: false, }) out2 := b2.String() if !strings.Contains(out2, "scanning is OFF") || !strings.Contains(out2, "carlos system antivirus enable") { t.Fatalf("disabled output does not name the command:\n%s", out2) } if strings.Contains(out2, "CARLOS_CLAMD_ADDR") { t.Fatalf("a wired-but-off deployment was told to fix its wiring:\n%s", out2) } // Wired and on. var b3 bytes.Buffer printSystemAntivirus(&b3, apiSystemAntivirusReply{ Wired: true, EngineWired: "clamav", Signature: fresh, Enabled: true, By: "op@example.com", At: "2026-09-02T08:00:00Z", }) out3 := b3.String() if !strings.Contains(out3, "scanning is ON") || !strings.Contains(out3, "op@example.com") { t.Fatalf("enabled output:\n%s", out3) } // And it must not imply anything is being scanned yet. if !strings.Contains(out3, "scans nothing") { t.Fatalf("enabled output implies coverage it does not have:\n%s", out3) } } // An engine that is wired but not answering is its own state, distinct from // both "no engine" and "switched off". func TestPrintSystemAntivirusReportsUnreachableEngine(t *testing.T) { var b bytes.Buffer printSystemAntivirus(&b, apiSystemAntivirusReply{ Wired: true, EngineError: "dial tcp scanner:3310: connection refused", Enabled: true, }) out := b.String() if !strings.Contains(out, "not answering") { t.Fatalf("an unreachable engine was not reported:\n%s", out) } // The policy half still reports, because it is still true. if !strings.Contains(out, "scanning is ON") { t.Fatalf("an engine failure hid the recorded policy:\n%s", out) } } // A record the console could not read is not "off". Reporting it as off would // be a guess, on the one command an operator runs to find out what is true. func TestPrintSystemAntivirusReportsUnreadableRecord(t *testing.T) { var b bytes.Buffer printSystemAntivirus(&b, apiSystemAntivirusReply{ Wired: true, EngineWired: "clamav", RecordError: "schema 99 is not 1", }) out := b.String() if !strings.Contains(out, "could not be read") { t.Fatalf("an unreadable record was not reported:\n%s", out) } if strings.Contains(out, "scanning is OFF") { t.Fatalf("an unreadable record was reported as off:\n%s", out) } } // Stale definitions are called out here too, not only in store scan status: a // deployment-wide view is where an operator would notice freshclam had died. func TestPrintSystemAntivirusWarnsOnStaleSignatures(t *testing.T) { old := "ClamAV 1.4.1/27000/" + time.Now().Add(-90*24*time.Hour).Format(time.ANSIC) var b bytes.Buffer printSystemAntivirus(&b, apiSystemAntivirusReply{ Wired: true, EngineWired: "clamav", Signature: old, Enabled: true, MaxSignatureAgeSeconds: int64((168 * time.Hour) / time.Second), }) if !strings.Contains(b.String(), "days old") { t.Fatalf("stale definitions were not called out:\n%s", b.String()) } } func TestPrintSystemLayoutStatus(t *testing.T) { var b strings.Builder printSystemLayoutStatus(&b, systemLayoutResponse{Mode: "none"}) for _, want := range []string{"no layout intent recorded; boxes use the nested layout by default once their isolation is ready", "CARLOS_LAYOUT_DEFAULT=flat", "carlos system layout revert", "carlos status --layout"} { if !strings.Contains(b.String(), want) { t.Errorf("none lacks %q: %q", want, b.String()) } } b.Reset() printSystemLayoutStatus(&b, systemLayoutResponse{Mode: "nested", Box: "kass-1", RequestedAt: "2026-10-06T10:00:00Z", Force: true}) for _, want := range []string{"intent: nested for box kass-1", "2026-10-06T10:00:00Z, force", "carlos status --layout"} { if !strings.Contains(b.String(), want) { t.Errorf("output lacks %q:\n%s", want, b.String()) } } b.Reset() printSystemLayoutStatus(&b, systemLayoutResponse{Mode: "flat", RequestedAt: "2026-10-06T10:00:00Z"}) if !strings.Contains(b.String(), "intent: flat for every box (recorded 2026-10-06T10:00:00Z)") { t.Errorf("fleet: %q", b.String()) } } func TestSystemFilesSeedCommandEntryRefusesBeforeEffects(t *testing.T) { bin := buildCarlos(t) for _, flags := range []string{"", "1"} { cmd := exec.Command(bin, "system", "files", "seed", "--host", "seed.example") cmd.Dir = t.TempDir() cmd.Env = carlosTestEnv(t, "CARLOS_HOST_CLAIM="+flags, "CARLOS_HOST_CLAIM_ORIGINS="+flags, "CARLOS_MOVES_PHASE1=", "CARLOS_DEPLOYMENT_BUCKET=", "CARLOS_DEPLOYMENT_DIR="+t.TempDir(), "AWS_EC2_METADATA_DISABLED=true") out, err := cmd.CombinedOutput() var exit *exec.ExitError if !errors.As(err, &exit) || exit.ExitCode() != 1 || !strings.Contains(string(out), "Files seed failed:") { t.Fatalf("dispatch: %v %s", err, out) } } } // Issue #316: status says when bootstrap/carlos is not stable's build, and is // quiet when it is, when there is nothing to compare, and under --channel // canary. func TestPrintSystemStatusSaysWhenBootstrapLags(t *testing.T) { stable := map[string]*systemPointerRow{"stable": {Version: "v0.28.0"}} behind := "bootstrap/carlos is not stable's build (v0.28.0), so a new box would boot a different one. Copy bootstrap/router/v0.28.0/carlos-linux-arm64 over it." for _, tc := range []struct { name, channel, want string row *systemBootstrapRow }{ {"behind", "", behind, &systemBootstrapRow{Version: "v0.28.0", Key: "bootstrap/router/v0.28.0/carlos-linux-arm64", State: "behind"}}, {"behind, stable only", "stable", behind, &systemBootstrapRow{Version: "v0.28.0", Key: "bootstrap/router/v0.28.0/carlos-linux-arm64", State: "behind"}}, {"unreadable", "", "bootstrap/carlos could not be read, so this can't say what a new box would boot: access denied", &systemBootstrapRow{Version: "v0.28.0", State: "unreadable", Error: "access denied"}}, {"current", "", "", &systemBootstrapRow{Version: "v0.28.0", State: "current"}}, {"nothing to compare", "", "", nil}, {"canary only", "canary", "", &systemBootstrapRow{Version: "v0.28.0", State: "behind"}}, } { t.Run(tc.name, func(t *testing.T) { var b strings.Builder printSystemStatus(&b, systemStatusResponse{Pointers: stable, Bootstrap: tc.row}, tc.channel) out := b.String() if tc.want == "" { if strings.Contains(out, "bootstrap/carlos") { t.Fatalf("output mentions bootstrap/carlos:\n%s", out) } return } if !strings.Contains(out, tc.want) { t.Fatalf("output lacks %q:\n%s", tc.want, out) } }) } }