Files
mesh-controller/internal/catalogue/seats_declared_test.go
T
mesh-tools 97a2d841c7 Land feature 57's controller work on its release branch
The controller plans config, templates and moved providers' provisions,
hands bus accounts to the vault and judges the new manifest words, on the
feature's SDK and node-engine validator (feature 57, config and provisioning).
2026-10-15 21:27:33 +00:00

210 lines
9.2 KiB
Go

package catalogue
import (
"strings"
"testing"
)
func problemsFor(t *testing.T, shelf Shelf) string {
t.Helper()
return strings.Join(CatalogueProblems(shelf), "; ")
}
func telegram() Manifest {
return Manifest{Module: "telegram", Tools: []string{"status"}, DefinesSeats: []SeatDeclaration{{
Name: "telegram-sender", Scope: ScopeMesh,
Accepts: []string{"send"}, Emits: []string{"delivered", "failed"}, Serves: []Verb{{Name: "status"}},
}}, Claims: []Claim{{Name: "telegram-sender", Scope: ScopeMesh}}}
}
// The whole point: a module contributes a capability without the mesh being changed.
func TestAModuleDeclaresItsOwnSeatAndHoldsIt(t *testing.T) {
shop := Manifest{Module: "shop", Uses: []string{"telegram-sender"}}
if got := problemsFor(t, Shelf{"telegram": telegram(), "shop": shop}); got != "" {
t.Fatalf("a declared seat and its caller were refused: %s", got)
}
}
// The prefix is the reservation rule, so there is no list to maintain and none to drift.
func TestAModuleCannotDeclareAMeshSeat(t *testing.T) {
for _, n := range []string{"mesh-broker", "mesh-anything", "mesh-store"} {
m := Manifest{Module: "impostor", DefinesSeats: []SeatDeclaration{{Name: n, Accepts: []string{"x"}}}}
got := strings.Join(declaredSeatProblems(m), "; ")
if !strings.Contains(got, "reserved to the mesh") {
t.Fatalf("%q was accepted as a module's seat: %q", n, got)
}
}
}
// A seat name meaning two protocols is the failure nobody could diagnose afterwards.
func TestTwoModulesCannotDeclareTheSameSeat(t *testing.T) {
other := Manifest{Module: "aardvark", DefinesSeats: []SeatDeclaration{{
Name: "telegram-sender", Scope: ScopeMesh, Accepts: []string{"something-else"}}}}
got := problemsFor(t, Shelf{"telegram": telegram(), "aardvark": other})
if !strings.Contains(got, "already declares") {
t.Fatalf("both declarations stood: %s", got)
}
// The first declarer keeps it; only the second is refused.
if strings.Count(got, "already declares") != 1 {
t.Fatalf("expected exactly one refusal: %s", got)
}
}
// Where ADR 0110's guarantee lands under a derived set: a typo is refused, not resolved to
// nothing at runtime.
func TestUsingASeatNobodyDeclaresIsRefused(t *testing.T) {
shop := Manifest{Module: "shop", Uses: []string{"telegram-sendr"}}
got := problemsFor(t, Shelf{"telegram": telegram(), "shop": shop})
if !strings.Contains(got, "telegram-sendr") || !strings.Contains(got, "no module declares") {
t.Fatalf("a misspelled seat was accepted: %s", got)
}
}
// A holder that does not answer what the seat promises is a caller's timeout, found here instead.
func TestAHolderMustServeWhatItsSeatPromises(t *testing.T) {
m := telegram()
m.Tools = nil // declares the seat, serves none of it
got := problemsFor(t, Shelf{"telegram": m})
if !strings.Contains(got, "does not serve status") {
t.Fatalf("a holder was accepted that answers nothing its seat promises: %s", got)
}
}
// The claim may say what it serves for the role instead, when the module's own tools are not the
// seat's verbs (ADR 0160).
func TestAClaimMayNameWhatItServesForTheSeat(t *testing.T) {
m := telegram()
m.Tools = []string{"telegram_send"}
m.Claims = append([]Claim(nil), m.Claims...)
for i := range m.Claims {
if m.Claims[i].Name == "telegram-sender" {
m.Claims[i].Serves = []string{"status"}
}
}
if got := problemsFor(t, Shelf{"telegram": m}); strings.Contains(got, "does not serve") {
t.Fatalf("a claim naming the seat's verb was refused: %s", got)
}
}
// A seat with no protocol is a marker: which module is this node's showcase, or its packet filter.
// Most node-scoped seats are markers, so refusing one would refuse the majority of the set.
func TestASeatWithoutAProtocolIsAMarkerNotAMistake(t *testing.T) {
m := Manifest{Module: "vague", DefinesSeats: []SeatDeclaration{{Name: "something", Scope: ScopeNode}}}
if got := strings.Join(declaredSeatProblems(m), "; "); got != "" {
t.Fatalf("a marker seat was refused: %s", got)
}
}
// A claim at the wrong scope is a different seat than the one declared.
func TestAClaimMustMatchTheDeclaredScope(t *testing.T) {
m := telegram()
m.Claims = []Claim{{Name: "telegram-sender", Scope: ScopeNode}}
got := problemsFor(t, Shelf{"telegram": m})
if !strings.Contains(got, "scope") {
t.Fatalf("a claim at the wrong scope was accepted: %s", got)
}
}
// The mesh's own seats still work, and are not shadowed by the derived half.
func TestTheMeshsOwnSeatsAreStillClaimable(t *testing.T) {
// It delivers the bus, so its holder provides the bus — the rule this check now enforces.
m := Manifest{Module: "nats",
Provides: []Offer{{Name: "mesh-bus", Scope: ScopeMesh}},
Claims: []Claim{{Name: "mesh-broker", Scope: ScopeMesh}}}
if got := problemsFor(t, Shelf{"nats": m}); got != "" {
t.Fatalf("a mesh seat was refused by the derived check: %s", got)
}
}
// Only a node seat may carry needs (novox/hq ADR 0378 §8): a per-mesh seat's holder is not on any
// one node to depend on anything beside it there.
func TestOnlyANodeSeatMayCarryNeeds(t *testing.T) {
m := Manifest{Module: "mail", DefinesSeats: []SeatDeclaration{{
Name: "mail-notifier", Scope: ScopeMesh, Needs: []string{"node-notifier"}}}}
got := strings.Join(declaredSeatProblems(m), "; ")
if !strings.Contains(got, "needing") || !strings.Contains(got, "node seat") {
t.Fatalf("a mesh-scope seat's needs were accepted: %q", got)
}
}
// A seat needing itself names nothing (novox/hq ADR 0378 §8).
func TestASeatCannotNeedItself(t *testing.T) {
m := Manifest{Module: "mail", DefinesSeats: []SeatDeclaration{{
Name: "mail-notifier", Scope: ScopeNode, Needs: []string{"mail-notifier"}}}}
got := strings.Join(declaredSeatProblems(m), "; ")
if !strings.Contains(got, "needing itself") {
t.Fatalf("a seat needing itself was accepted: %q", got)
}
}
// A need naming nothing node-scope — undeclared, or declared at another scope — is the whole
// catalogue's to say (novox/hq ADR 0378 §8).
func TestANeedMustNameANodeSeatSomewhereInTheCatalogue(t *testing.T) {
mail := Manifest{Module: "mail", DefinesSeats: []SeatDeclaration{{
Name: "mail-notifier", Scope: ScopeNode, Needs: []string{"desk-beeper"}}}}
// Nobody declares desk-beeper at all.
got := problemsFor(t, Shelf{"mail": mail})
if !strings.Contains(got, "desk-beeper") || !strings.Contains(got, "no module declares") {
t.Fatalf("needing an undeclared seat was accepted: %q", got)
}
// Declared, but at mesh scope: still not a node seat to hold beside it.
notNode := Manifest{Module: "dunst", DefinesSeats: []SeatDeclaration{{Name: "desk-beeper", Scope: ScopeMesh}}}
got = problemsFor(t, Shelf{"mail": mail, "dunst": notNode})
if !strings.Contains(got, "desk-beeper") || !strings.Contains(got, "not a node seat") {
t.Fatalf("needing a mesh-scope seat was accepted: %q", got)
}
// Declared at node scope: accepted.
dunst := Manifest{Module: "dunst", DefinesSeats: []SeatDeclaration{{Name: "desk-beeper", Scope: ScopeNode}}}
if got := problemsFor(t, Shelf{"mail": mail, "dunst": dunst}); got != "" {
t.Fatalf("needing a node seat another module declares was refused: %q", got)
}
}
// A refusal that reorders itself between runs is a refusal nobody can diff.
func TestTheProblemsAreStable(t *testing.T) {
shelf := Shelf{"telegram": telegram(), "shop": {Module: "shop", Uses: []string{"nope"}},
"other": {Module: "other", Uses: []string{"also-nope"}}}
first, second := problemsFor(t, shelf), problemsFor(t, shelf)
if first != second {
t.Fatalf("unstable:\n%s\n%s", first, second)
}
}
// **A build machine has no store, so it may not judge a seat.** The set is data the control plane
// owns (novox/hq ADR 0122), and `ParseManifest` runs on the build machine too, against whatever set
// that binary was compiled with. When the two disagreed, a valid holder of the bus seat was refused
// mid-rollout — the compiled row said the seat delivered one provision, the store's row said
// another, and the build failed on the copy rather than the truth. The parser judges the manifest;
// the seat set judges the claim, where it is loaded.
func TestTheParserDoesNotJudgeWhatOnlyTheStoreKnows(t *testing.T) {
was := Seats()
t.Cleanup(func() { UseSeats(was) })
// A store whose bus seat delivers something this module does provide.
UseSeats([]Seat{{Name: "mesh-broker", Scope: ScopeMesh, Delivers: "mesh-bus", Decision: "test"}})
raw := []byte(`{"module":"a-bus","version":"1",` +
`"provides":[{"name":"mesh-bus","scope":"mesh"}],` +
`"claims":[{"name":"mesh-broker","scope":"mesh"}]}`)
m, err := ParseManifest(raw)
if err != nil {
t.Fatalf("the parser refused a claim only the seat set can judge: %v", err)
}
if got := CatalogueProblems(Shelf{m.Module: m}); len(got) != 0 {
t.Fatalf("a holder that provides what the store says the seat delivers was refused: %v", got)
}
// And with the store saying the seat delivers something else, registration is what refuses it.
UseSeats([]Seat{{Name: "mesh-broker", Scope: ScopeMesh, Delivers: "other-bus", Decision: "test"}})
if _, err := ParseManifest(raw); err != nil {
t.Fatalf("the parser judged it the second time: %v", err)
}
got := strings.Join(CatalogueProblems(Shelf{m.Module: m}), "; ")
if !strings.Contains(got, `does not provide "other-bus"`) {
t.Fatalf("registration did not refuse a holder that cannot answer for the seat: %q", got)
}
}