package main import ( "encoding/json" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/inventory" ) // The shape something other than a person reads. // // Hard to change once anything is built against it, so it stays close to what the domain already // calls things and carries no summary field that would have to be kept true. func statusOf(t *testing.T, wrong []inventory.Doing, nodes, quiet []inventory.Node, behind map[string][]string, sources map[string]inventory.Source) map[string]any { t.Helper() body, err := statusAsJSON(answers{ wrong: wrong, nodes: nodes, quiet: quiet, behind: behind, sources: sources}) if err != nil { t.Fatal(err) } var parsed map[string]any if err := json.Unmarshal(body, &parsed); err != nil { t.Fatalf("what a board would read is not JSON: %v", err) } return parsed } func TestRefusedAndFailedStayDistinctAllTheWayOut(t *testing.T) { // They are fixed in different places, so one word for both would send half the readers of a // page to the wrong one. got := statusOf(t, []inventory.Doing{ {Node: "one", Outcome: inventory.OutcomeRefused, Refused: "a file needs a path"}, {Node: "two", Outcome: inventory.OutcomeFailed, Applied: 3, Failed: []inventory.FailedResource{{ID: "shell.pkg", Error: "target not found"}}}, }, []inventory.Node{{Name: "one"}, {Name: "two"}}, nil, nil, nil) wrong, _ := got["wrong"].([]any) if len(wrong) != 2 { t.Fatalf("got %v", got["wrong"]) } first, _ := wrong[0].(map[string]any) if first["outcome"] != inventory.OutcomeRefused || first["refused"] == nil { t.Fatalf("a refusal did not survive: %v", first) } second, _ := wrong[1].(map[string]any) if second["outcome"] != inventory.OutcomeFailed { t.Fatalf("a failure did not survive: %v", second) } if second["applied"] != float64(3) { // "Three of eight" and "none of eight" are different machines. t.Fatalf("what did apply was not carried: %v", second) } } func TestAMachineThatNeverSpokeSaysSoByOmission(t *testing.T) { // Never heard from and quiet for a while are different situations, and a zero time would read // as a date in 1970 on any page that formatted it. got := statusOf(t, nil, []inventory.Node{{Name: "silent"}, {Name: "away", LastSeen: time.Now().Add(-3 * time.Hour)}}, []inventory.Node{{Name: "silent"}, {Name: "away", LastSeen: time.Now().Add(-3 * time.Hour)}}, nil, nil) quiet, _ := got["quiet"].([]any) if len(quiet) != 2 { t.Fatalf("got %v", got["quiet"]) } never, _ := quiet[0].(map[string]any) if _, said := never["lastSeen"]; said { t.Fatalf("a machine that never spoke carries a time: %v", never) } away, _ := quiet[1].(map[string]any) if _, said := away["lastSeen"]; !said { t.Fatalf("a machine that has been quiet carries no time: %v", away) } } func TestNothingWrongIsAnEmptyListRatherThanNothing(t *testing.T) { // A page distinguishing "no machines are wrong" from "this field is missing" would have to // handle both, and null is the one that gets forgotten. got := statusOf(t, nil, []inventory.Node{{Name: "a", LastSeen: time.Now()}}, nil, nil, nil) for _, key := range []string{"wrong", "quiet", "behind"} { list, ok := got[key].([]any) if !ok { t.Fatalf("%q is %T, not a list", key, got[key]) } if len(list) != 0 { t.Fatalf("%q is not empty: %v", key, list) } } // And how many machines there are, so a reader can tell "none wrong" from "none at all". if got["machines"] != float64(1) { t.Fatalf("got %v", got["machines"]) } } func TestWhatIsBehindNamesTheMachinesRunningTheOldOne(t *testing.T) { // A module being out of date is a fact about the catalogue; machines running the old one is // the thing with consequences. got := statusOf(t, nil, nil, nil, map[string][]string{"shell": {"workstation", "laptop"}}, map[string]inventory.Source{"shell": {BuiltFrom: "aaaaaaa1", Head: "bbbbbbb2"}}) behind, _ := got["behind"].([]any) if len(behind) != 1 { t.Fatalf("got %v", got["behind"]) } row, _ := behind[0].(map[string]any) if row["module"] != "shell" || row["builtFrom"] != "aaaaaaa1" || row["head"] != "bbbbbbb2" { t.Fatalf("got %v", row) } on, _ := row["on"].([]any) if len(on) != 2 { t.Fatalf("the machines running the old one are not named: %v", row) } } func TestNoSecretIsInWhatABoardReads(t *testing.T) { // Everything here comes from the mesh's own records, which hold no readable secret — but a // shape a page is built against is exactly where one would eventually be added for // convenience, so this says it out loud. body, err := statusAsJSON(answers{ wrong: []inventory.Doing{{Node: "a", Outcome: inventory.OutcomeRefused, Refused: "resource \"x\": a file needs a path"}}, nodes: []inventory.Node{{Name: "a"}}, refused: map[string]string{"a": "nothing provides \"database\", wanted by web"}, }) if err != nil { t.Fatal(err) } for _, word := range []string{"password", "secret", "sealed", "credential", "token"} { if strings.Contains(strings.ToLower(string(body)), word) { t.Fatalf("what a board reads carries a %q field:\n%s", word, body) } } } // A failure that has been reported identically enough times is said to be stuck, with when it // began and how many times — so a board can tell a machine looping on something that will never // apply from one that failed a minute ago (novox/hq 04-ISSUES/065, ADR 0090). func TestAMachineFailingTheSameWayIsSaidToBeStuck(t *testing.T) { began := time.Date(2026, 9, 21, 9, 0, 0, 0, time.UTC) got := statusOf(t, []inventory.Doing{ {Node: "looping", Outcome: inventory.OutcomeFailed, Since: &began, Times: inventory.StuckAfter, Failed: []inventory.FailedResource{{ID: "img", Error: "no such image"}}}, {Node: "once", Outcome: inventory.OutcomeFailed, Since: &began, Times: 1, Failed: []inventory.FailedResource{{ID: "img", Error: "no such image"}}}, }, []inventory.Node{{Name: "looping"}, {Name: "once"}}, nil, nil, nil) wrong, _ := got["wrong"].([]any) looping, _ := wrong[0].(map[string]any) if looping["stuck"] != true || looping["times"] != float64(inventory.StuckAfter) { t.Fatalf("three identical failures are stuck: %v", looping) } if since, _ := looping["since"].(string); !strings.HasPrefix(since, "2026-09-21T09:00:00") { t.Fatalf("when it began was not carried: %v", looping) } once, _ := wrong[1].(map[string]any) if once["stuck"] != false || once["times"] != float64(1) { t.Fatalf("one failure is not stuck: %v", once) } }