ADR 0593 §7: the composer with its refusals leaves mesh-sdk, so mesh-sdk carries no composition rule. The catalogue functions only the node-engine's composition reaches (Resolve, Resolution's Compose, Declaration, DataItems, LeftOut, Rules and the rest, the settings, placeholder and template refusals, the rendering helpers) are copied with their tests into internal/compose/resolution; a method of a mesh-sdk shape becomes a function taking it first. The composition calls its own copies, and the captured catalogue its tests read lives here now, shared by mesh-compose's tests. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
111 lines
4.4 KiB
Go
111 lines
4.4 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}}}
|
|
}
|
|
|
|
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}}}
|
|
}
|
|
|
|
// 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])
|
|
}
|
|
}
|