mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/mounts-module delivered: every member is delivered
162 lines
6.1 KiB
Go
162 lines
6.1 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Defends novox/hq ADR 0263: a machine's shares and the shares a machine mounts are two node seats, each
|
|
// with its verbs required of every holder, each delivering nothing — `nfs-share` is provided at the mesh's
|
|
// scope by the holder of node-nfs-server, and a node seat cannot answer for a provision at the mesh's.
|
|
|
|
func TestTheSharesAndMountsAreNodeSeatsWithTheirVerbs(t *testing.T) {
|
|
for seat, want := range map[string]string{
|
|
NFSServerSeat: "exports clients test reload adopt",
|
|
MountsSeat: "list test mount unmount adopt",
|
|
} {
|
|
s, ok := SeatNamed(seat)
|
|
if !ok {
|
|
t.Fatalf("%s is not in the mesh's set", seat)
|
|
}
|
|
if s.Scope != ScopeNode || s.Decision != "novox/hq ADR 0263" || s.Delivers != "" || s.Replicated {
|
|
t.Errorf("%s is %+v; a node seat under ADR 0263 that delivers nothing", seat, s)
|
|
}
|
|
var got []string
|
|
for _, v := range s.Serves {
|
|
got = append(got, v.Name)
|
|
if v.Optional {
|
|
t.Errorf("%s.%s is optional; its first holder serves it", seat, v.Name)
|
|
}
|
|
if v.Description == "" || v.Input["type"] != "object" || len(v.Replaces) == 0 {
|
|
t.Errorf("%s.%s has no description, no object schema or says it replaces nothing", seat, v.Name)
|
|
}
|
|
}
|
|
if strings.Join(got, " ") != want {
|
|
t.Errorf("%s serves %v, not %s", seat, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Both acts that take over what a person wrote by hand are dry runs unless confirmed, and name the path.
|
|
func TestTheAdoptVerbsAreDryRunsUnlessConfirmed(t *testing.T) {
|
|
for seat, key := range map[string]string{NFSServerSeat: "path", MountsSeat: "mountpoint"} {
|
|
s, _ := SeatNamed(seat)
|
|
for _, v := range s.Serves {
|
|
if v.Name != "adopt" {
|
|
continue
|
|
}
|
|
props, _ := v.Input["properties"].(map[string]any)
|
|
confirm, _ := props["confirm"].(map[string]any)
|
|
if confirm == nil || confirm["enum"] == nil {
|
|
t.Errorf("%s.adopt has no confirm switch: %v", seat, v.Input)
|
|
}
|
|
if req, _ := v.Input["required"].([]string); len(req) != 1 || req[0] != key {
|
|
t.Errorf("%s.adopt requires %v, not %q alone", seat, v.Input["required"], key)
|
|
}
|
|
if !strings.Contains(v.Description, "Without confirm, says what it would do and changes nothing") {
|
|
t.Errorf("%s.adopt does not say a call without confirm changes nothing: %s", seat, v.Description)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// A holder claiming the seat at a node with every verb holds it; one naming a verb the seat does not
|
|
// promise, or leaving one out, is refused — as the catalogue's nfs-server and mounts modules claim them.
|
|
func TestAHolderOfTheShareSeatsServesEveryVerbAndNothingElse(t *testing.T) {
|
|
cases := []struct {
|
|
seat, module string
|
|
verbs []string
|
|
}{
|
|
{NFSServerSeat, "nfs-server", []string{"exports", "clients", "test", "reload", "adopt"}},
|
|
{MountsSeat, "mounts", []string{"list", "test", "mount", "unmount", "adopt"}},
|
|
}
|
|
for _, c := range cases {
|
|
seat, _ := SeatNamed(c.seat)
|
|
holder := Manifest{Module: c.module, Version: "1",
|
|
Claims: []Claim{{Name: c.seat, Scope: ScopeNode, Serves: c.verbs}}}
|
|
if c.seat == NFSServerSeat {
|
|
holder.Provides = []Offer{{Name: "nfs-share", Scope: ScopeMesh}}
|
|
}
|
|
if err := CanHold(holder, seat); err != nil {
|
|
t.Errorf("%s serving every verb is refused: %v", c.module, err)
|
|
}
|
|
typo := holder
|
|
typo.Claims = []Claim{{Name: c.seat, Scope: ScopeNode, Serves: append(append([]string{}, c.verbs...), "export")}}
|
|
if err := CanHold(typo, seat); err == nil || !strings.Contains(err.Error(), "does not promise") {
|
|
t.Errorf("%s naming a verb the seat does not promise was accepted: %v", c.module, err)
|
|
}
|
|
short := holder
|
|
short.Claims = []Claim{{Name: c.seat, Scope: ScopeNode, Serves: c.verbs[:len(c.verbs)-1]}}
|
|
if err := CanHold(short, seat); err == nil || !strings.Contains(err.Error(), "adopt") {
|
|
t.Errorf("%s leaving adopt out was accepted: %v", c.module, err)
|
|
}
|
|
mesh := holder
|
|
mesh.Claims = []Claim{{Name: c.seat, Scope: ScopeMesh, Serves: c.verbs}}
|
|
if err := CanHold(mesh, seat); err == nil {
|
|
t.Errorf("%s claiming a node seat at the mesh's scope was accepted", c.module)
|
|
}
|
|
}
|
|
}
|
|
|
|
// What each verb replaces is what an agent would type over ssh to read or change a share by hand.
|
|
func TestTheShareVerbsSayWhatTheyReplace(t *testing.T) {
|
|
want := map[string]string{
|
|
NFSServerSeat + ".exports": "exportfs -v",
|
|
NFSServerSeat + ".adopt": "zfs set sharenfs=off",
|
|
MountsSeat + ".list": "cat /etc/fstab",
|
|
MountsSeat + ".adopt": "sed -i /etc/fstab",
|
|
}
|
|
for _, s := range DefaultSeats() {
|
|
for _, v := range s.Serves {
|
|
cmd, ok := want[s.Name+"."+v.Name]
|
|
if !ok {
|
|
continue
|
|
}
|
|
delete(want, s.Name+"."+v.Name)
|
|
found := false
|
|
for _, r := range v.Replaces {
|
|
found = found || r == cmd
|
|
}
|
|
if !found {
|
|
t.Errorf("%s.%s does not say it replaces %q: %v", s.Name, v.Name, cmd, v.Replaces)
|
|
}
|
|
}
|
|
}
|
|
for verb := range want {
|
|
t.Errorf("%s is not a verb of the compiled seats", verb)
|
|
}
|
|
}
|
|
|
|
// The confirm switch is the seat's string "true", as every verb's switch is, and its description says so:
|
|
// a holder handed the boolean reading of "true to change it" would treat the string as a dry run.
|
|
func TestTheConfirmSwitchIsTheStringTrue(t *testing.T) {
|
|
for _, seat := range []string{NFSServerSeat, MountsSeat} {
|
|
s, _ := SeatNamed(seat)
|
|
for _, v := range s.Serves {
|
|
props, _ := v.Input["properties"].(map[string]any)
|
|
confirm, _ := props["confirm"].(map[string]any)
|
|
if confirm == nil {
|
|
continue
|
|
}
|
|
if confirm["type"] != "string" {
|
|
t.Errorf("%s.%s confirm is %v, not the string switch", seat, v.Name, confirm["type"])
|
|
}
|
|
if d, _ := confirm["description"].(string); !strings.Contains(d, `"true"`) {
|
|
t.Errorf("%s.%s confirm does not name the string \"true\": %q", seat, v.Name, d)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// reload has the kernel read the export files; the module's process writes its file. The description
|
|
// must not promise a write it does not do.
|
|
func TestReloadSaysWhatItDoes(t *testing.T) {
|
|
s, _ := SeatNamed(NFSServerSeat)
|
|
for _, v := range s.Serves {
|
|
if v.Name == "reload" && (strings.Contains(v.Description, "Write this machine's export file") ||
|
|
!strings.Contains(v.Description, "exportfs")) {
|
|
t.Errorf("reload's description promises a write or does not name exportfs: %s", v.Description)
|
|
}
|
|
}
|
|
}
|