package main import ( "context" "net" "slices" "strings" "testing" "time" "golang.org/x/net/dns/dnsmessage" "git.novox.be/novox/mesh-sdk/go/catalogue" "github.com/novox/mesh-controller/internal/broker" "github.com/novox/mesh-controller/internal/conditions" "github.com/novox/mesh-controller/internal/link" ) // The self-check (novox/hq to-be 45 §4): a probe that fails raises its condition, one that cannot run // raises probe-failed for itself and is never a pass, and every run ends with its heartbeat. // withProbes runs the test with a registry of its own. func withProbes(t *testing.T, probes ...probe) { t.Helper() before := probeRegistry probeRegistry = probes t.Cleanup(func() { probeRegistry = before }) } // **The registry says what each probe asserts**, and a probe not built says why and when. func TestTheRegistryIsTheDesignsLiveForm(t *testing.T) { seen := map[string]bool{} for _, p := range probeRegistry { if seen[p.ID] { t.Errorf("%s twice", p.ID) } seen[p.ID] = true if p.Asserts == "" || p.From == "" || p.Kind == "" { t.Errorf("%s does not say what it asserts, where from, or what it raises", p.ID) } if (p.run == nil) != (p.Deferred != "") || (p.Deferred != "" && p.Phase <= 1) { t.Errorf("%s is run and deferred, or neither, or deferred out of Phase 1: %+v", p.ID, p) } } for _, id := range []string{"D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10"} { if !seen[id] { t.Errorf("to-be 45 §4 has %s and the registry does not", id) } } } // **The doctor and the watchdogs watch each other**: watchdogs that stopped are DW; a self-check that // stopped is S10 (signals_test.go). func TestWatchdogsThatStoppedAreSaid(t *testing.T) { w := &watchdogs{started: time.Now().Add(-time.Hour)} d := &doctor{watchdogs: w, host: "anchor"} got, err := probeWatchdogs(t.Context(), d) if err != nil || len(got) != 1 || got[0].Severity != conditions.Urgent { t.Fatalf("%+v %v", got, err) } w.ticked = time.Now() if got, _ := probeWatchdogs(t.Context(), d); len(got) != 0 { t.Fatalf("%+v", got) } } // **D1 composes every machine of a healthy mesh and the host's own validator takes each.** // **D1 names a machine nothing can be sent to**, and the network that cannot be computed for it. // **D2: a resolver answering NXDOMAIN for IPv6 is wrong** — musl takes it as no such name (issue 262). func TestAResolverAnsweringNoSuchNameForIPv6IsWrong(t *testing.T) { answerAs := func(rcode dnsmessage.RCode) string { conn, err := net.ListenPacket("udp", "127.0.0.1:0") if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = conn.Close() }) go func() { buf := make([]byte, 1500) for { n, from, err := conn.ReadFrom(buf) if err != nil { return } var q dnsmessage.Message if q.Unpack(buf[:n]) != nil { continue } reply := dnsmessage.Message{Header: dnsmessage.Header{ID: q.ID, Response: true}, Questions: q.Questions} if q.Questions[0].Type == dnsmessage.TypeA { reply.Answers = []dnsmessage.Resource{{Header: dnsmessage.ResourceHeader{Name: q.Questions[0].Name, Type: dnsmessage.TypeA, Class: dnsmessage.ClassINET}, Body: &dnsmessage.AResource{A: [4]byte{10, 77, 0, 1}}}} } else { reply.RCode = rcode } packed, _ := reply.Pack() _, _ = conn.WriteTo(packed, from) } }() _, port, _ := net.SplitHostPort(conn.LocalAddr().String()) return port } before := resolverPort t.Cleanup(func() { resolverPort = before }) resolverPort = answerAs(dnsmessage.RCodeSuccess) v4, rcode, err := askResolver(t.Context(), "127.0.0.1", "anchor.internal", dnsmessage.TypeA) if err != nil || rcode != dnsmessage.RCodeSuccess || !slices.Equal(v4, []string{"10.77.0.1"}) { t.Fatalf("%v %v %v", v4, rcode, err) } v6, rcode, err := askResolver(t.Context(), "127.0.0.1", "anchor.internal", dnsmessage.TypeAAAA) if err != nil || rcode != dnsmessage.RCodeSuccess || len(v6) != 0 { t.Fatalf("NODATA read as %v %v %v", v6, rcode, err) } resolverPort = answerAs(dnsmessage.RCodeNameError) if _, rcode, _ := askResolver(t.Context(), "127.0.0.1", "anchor.internal", dnsmessage.TypeAAAA); rcode != dnsmessage.RCodeNameError { t.Fatalf("NXDOMAIN read as %v", rcode) } } // **D6, D7: what the controller defines is what it finds**, and a consumer deleted or a stream // redefined is said — against a real bus, raised by the same derivation the controller starts with. // **S9 hears the bus**: a consumer that gives up on a message, and one deleted, as the server says. // **D10 compares a node-engine with what it is delivered as, not with its commit** (2026-10-06: every // machine read as behind right after a push sent it the current build — it says the digest-named // directory it runs from, and the mesh holds a commit). func TestANodeEngineIsJudgedByTheVersionItIsDeliveredAs(t *testing.T) { m := catalogue.Manifest{Module: "mesh-host", Resources: []map[string]any{ {"id": "launcher", "type": "file", "path": "/usr/lib/nox-mesh-host/launch"}, {"id": "host", "type": "archive", "path": "/usr/lib/nox-mesh-host/versions/31045596c83a"}, {"id": "unfilled", "type": "archive", "path": "/usr/lib/x/versions/${version}"}, }} delivered := deliveredVersions(m) if !slices.Equal(delivered, []string{"31045596c83a"}) { t.Fatalf("%v", delivered) } commit := "1545b00a9f0c" for _, c := range []struct { reported string behind bool }{ {"31045596c83a", false}, // the live case: current, and was called behind {"0123456789ab", true}, // another delivery {"1545b00a", false}, // placed by hand, stamped with the commit {"", false}, // not said } { if got := engineBehind(c.reported, delivered, commit); got != c.behind { t.Errorf("%q behind = %v, want %v", c.reported, got, c.behind) } } if engineBehind("31045596c83a", nil, commit) { t.Error("behind a mesh that holds no delivered build") } } // **Every seat verb a probe calls is one it declares, and one the controller is granted** — derived // from the registry, so a probe added with a question the bus would refuse fails here, not live. func TestEverySeatVerbAProbeAsksIsGranted(t *testing.T) { granted, err := broker.PermissionsFor(broker.Principal{Kind: broker.KindController, PasswordHash: "x"}) if err != nil { t.Fatal(err) } asked := 0 for _, p := range probeRegistry { for _, v := range p.Asks { asked++ subject := link.NodeSeatToolSubject(v.Seat, v.Verb, "anchor") if !slices.ContainsFunc(granted.Publish, func(pattern string) bool { return subjectMatches(pattern, subject) }) { t.Errorf("%s asks %s.%s and the controller may not publish %s", p.ID, v.Seat, v.Verb, subject) } if !slices.Contains(broker.VerbsTheSelfCheckAsks, v) { t.Errorf("%s asks %s.%s, which broker.VerbsTheSelfCheckAsks does not name", p.ID, v.Seat, v.Verb) } } } if asked == 0 { t.Fatal("no probe asks a seat verb: D8 lost its declaration") } // And a probe asking what it did not declare is refused before anything is sent. ctx := context.WithValue(t.Context(), probeAsksKey{}, probe{ID: "DX"}) if _, err := askSeatTool(ctx, nil, "node-intrusion-prevention", "banned", "anchor"); err == nil || !strings.Contains(err.Error(), "does not declare") { t.Fatalf("an undeclared question was asked: %v", err) } } // subjectMatches is the bus's matching of a permission pattern against a subject. func subjectMatches(pattern, subject string) bool { p, s := strings.Split(pattern, "."), strings.Split(subject, ".") for i, tok := range p { if tok == ">" { return len(s) > i } if i >= len(s) || (tok != "*" && tok != s[i]) { return false } } return len(p) == len(s) }