package main import ( "context" "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/inventory" ) // Whether a mesh could move its bus, read from a real store. // // The readiness reasoning has its own tests; this is about the gathering — that the question is // answered from what the mesh actually holds, on a store with machines and modules in it, without // writing anything. func TestReadinessIsGatheredFromWhatTheMeshHolds(t *testing.T) { inv := inventory.ForTest(t) ctx := context.Background() // A mesh mid-change: two machines, the bus module on one of them, a module that speaks and a // module that never does. for _, m := range []catalogue.Manifest{ {Module: "nats", Version: "1", BusUsers: "/var/lib/nats-module/conf/accounts.conf", Claims: []catalogue.Claim{{Name: "mesh-broker", Scope: catalogue.ScopeMesh}}}, {Module: "gitea", Version: "1", Tools: []string{"repo_create"}}, {Module: "wallpaper", Version: "1"}, } { if err := inv.RegisterModule(ctx, m, inventory.Source{Repository: "/r"}); err != nil { t.Fatal(err) } } for _, n := range []string{"anchor", "laptop"} { if _, err := inv.AddNode(ctx, n); err != nil { t.Fatal(err) } } for _, a := range [][2]string{{"anchor", "nats"}, {"anchor", "gitea"}, {"laptop", "wallpaper"}} { if _, err := inv.Assign(ctx, a[0], a[1]); err != nil { t.Fatal(err) } } state, err := readinessOf(ctx, inv) if err != nil { t.Fatal(err) } if state.Holder != "anchor" { t.Errorf("the machine holding the bus reads as %q", state.Holder) } if len(state.Nodes) != 2 { t.Errorf("machines read as %v", state.Nodes) } // **A module that never speaks is not counted as missing a credential.** A third of the catalogue // never reaches the bus, and listing those would bury the ones that matter. for _, m := range state.Modules { if strings.HasSuffix(m, "/wallpaper") { t.Errorf("a module that never speaks was counted: %v", state.Modules) } } if len(state.Modules) != 2 { t.Errorf("modules that speak read as %v; expected the bus module and the one with a tool", state.Modules) } // Nothing has been minted, so it is not ready — and it says so about each machine by name. why := notReadyOf(state) if len(why) == 0 { t.Fatal("a mesh where nothing has a credential was reported ready to move") } said := strings.Join(why, "\n") for _, name := range []string{"anchor", "laptop"} { if !strings.Contains(said, name) { t.Errorf("the refusal does not name %s: %s", name, said) } } // Mint for one machine and it drops out of the complaint, which is how somebody works through it. if _, err := inv.MintBusPassword(ctx, inventory.BusUser{ Username: "node.laptop", Kind: inventory.BusNode, Node: "laptop", }); err != nil { t.Fatal(err) } state, err = readinessOf(ctx, inv) if err != nil { t.Fatal(err) } if !state.Credentialled["laptop"] { t.Error("a machine that was minted a credential still reads as having none") } }