Files
mesh-host/internal/compose/resolution/holdings_test.go
T

124 lines
4.8 KiB
Go

package resolution
import (
"strings"
"testing"
mcat "git.novox.be/novox/mesh-sdk/go/catalogue"
)
func busSeatDelivering(t *testing.T, delivers string) {
t.Helper()
was := mcat.Seats()
t.Cleanup(func() { mcat.UseSeats(was) })
mcat.UseSeats([]mcat.Seat{{Name: "mesh-broker", Scope: mcat.ScopeMesh, Delivers: delivers, Decision: "test"}})
}
func oldBroker() mcat.Manifest {
return mcat.Manifest{Module: "old-broker", Provides: []mcat.Offer{{Name: "mesh-bus", Scope: mcat.ScopeMesh}},
Claims: []mcat.Claim{{Name: "mesh-broker", Scope: mcat.ScopeMesh, Serves: brokerVerbs()}}}
}
func newBroker() mcat.Manifest {
return mcat.Manifest{Module: "new-broker", Provides: []mcat.Offer{{Name: "mesh-bus", Scope: mcat.ScopeMesh}},
Claims: []mcat.Claim{{Name: "mesh-broker", Scope: mcat.ScopeMesh, Serves: brokerVerbs()}}}
}
// Nothing on record: exactly the old rule. One claimant holds; two are refused.
func TestWithNoHolderOnRecordTheSoleClaimantHoldsAndTwoAreRefused(t *testing.T) {
busSeatDelivering(t, "mesh-bus")
node := mcat.Node{Name: "anchor"}
held, problems := checkClaims([]mcat.Manifest{oldBroker()}, node, nil, nil)
if len(problems) != 0 || len(held) != 1 || held[0].Module != "old-broker" {
t.Fatalf("a sole claimant did not hold the seat: held=%v problems=%v", held, problems)
}
_, problems = checkClaims([]mcat.Manifest{oldBroker(), newBroker()}, node, nil, nil)
if len(problems) != 1 || !strings.Contains(problems[0], "both claim") {
t.Fatalf("two claimants with nothing on record were not refused: %v", problems)
}
}
// With a holder on record, the other eligible assignment is silent: not refused, and not holding.
func TestTheHolderOnRecordHoldsAndTheOtherClaimantStandsBesideIt(t *testing.T) {
busSeatDelivering(t, "mesh-bus")
node := mcat.Node{Name: "anchor"}
record := []mcat.Held{{Claim: "mesh-broker", Scope: mcat.ScopeMesh, Node: "anchor", Module: "new-broker"}}
held, problems := checkClaims([]mcat.Manifest{oldBroker(), newBroker()}, node, nil, record)
if len(problems) != 0 {
t.Fatalf("the assignment beside the holder was refused: %v", problems)
}
if len(held) != 1 || held[0].Module != "new-broker" {
t.Fatalf("the holder on record is not the one holding: %v", held)
}
}
// The record names a node too: an eligible module on another machine holds nothing, and its
// machine's set still resolves.
func TestAHolderOnRecordElsewhereLeavesThisMachinesClaimantSilent(t *testing.T) {
busSeatDelivering(t, "mesh-bus")
record := []mcat.Held{{Claim: "mesh-broker", Scope: mcat.ScopeMesh, Node: "anchor", Module: "new-broker"}}
held, problems := checkClaims([]mcat.Manifest{oldBroker()}, mcat.Node{Name: "laptop"}, nil, record)
if len(problems) != 0 || len(held) != 0 {
t.Fatalf("a claimant elsewhere than the recorded holder was not simply silent: held=%v problems=%v",
held, problems)
}
}
// A machine being moved is handed its membership for the new bus as a sealed file in its own
// declaration — the machine's, not any module's (design 28, task 5.2).
func TestAMembershipForTheNewBusIsComposedAsASealedFile(t *testing.T) {
r := mcat.Resolution{Node: "anchor"}
got, err := Compose(r, mcat.Rendering{BusMembership: "sealed-blob"})
if err != nil {
t.Fatal(err)
}
var found map[string]any
for _, res := range got.Resources {
if res["id"] == BusMembershipID() {
found = res
}
}
if found == nil {
t.Fatalf("no membership resource in %v", got.Resources)
}
if found["path"] != mcat.BusMembershipPath || found["sealed"] != "sealed-blob" || found["mode"] != "0600" {
t.Fatalf("the membership is not a sealed 0600 file where the host reads it: %v", found)
}
// And a machine not being moved is handed nothing.
got, _ = Compose(r, mcat.Rendering{})
for _, res := range got.Resources {
if res["id"] == BusMembershipID() {
t.Fatal("a machine with no membership on record was handed one")
}
}
}
func TestARefusalWithNothingOnRecordNamesTheHandover(t *testing.T) {
busSeatDelivering(t, "mesh-bus")
elsewhere := []mcat.Held{{Claim: "mesh-broker", Scope: mcat.ScopeMesh, Node: "anchor", Module: "old-broker"}}
_, problems := checkClaims([]mcat.Manifest{newBroker()}, mcat.Node{Name: "laptop"}, elsewhere, nil)
if len(problems) != 1 {
t.Fatalf("two derived claimants across machines were not refused: %v", problems)
}
if !strings.Contains(problems[0], "`seat mesh-broker --to anchor/old-broker`") {
t.Fatalf("the refusal does not name the handover that records the holder: %s", problems[0])
}
}
// brokerVerbs are the verbs the seat mesh-broker promises its holder, as the contracts define them: a holder names every one
// (ADR 0132), so a fixture holder does (issue 1966 ENG-3).
func brokerVerbs() []string {
seat, _ := mcat.SeatNamed("mesh-broker")
var out []string
for _, v := range seat.Serves {
if !v.Optional {
out = append(out, v.Name)
}
}
return out
}