A module may declare its own seats (novox/hq ADR 0118)
The manifest carries seats and uses; registration refuses a mesh-* name, a duplicate declarer, an undeclared uses or claim, a seat with no protocol, a scope mismatch, and a holder that does not answer what its seat promises. The parser stops judging unknown claim names, because it cannot: another module may declare that seat, and one manifest cannot tell. The test that encoded the old rule is rewritten to assert the refusal at registration, and a new one pins the case the parser could not have distinguished. Tools are declared for the first time, under their own key — serves already means a provision's facts.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
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"}, Seats: []SeatDeclaration{{
|
||||
Name: "telegram-sender", Scope: ScopeMesh,
|
||||
Accepts: []string{"send"}, Emits: []string{"delivered", "failed"}, Serves: []string{"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", Seats: []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", Seats: []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)
|
||||
}
|
||||
}
|
||||
|
||||
// A seat with no protocol is exclusion with nothing on the other side of it.
|
||||
func TestASeatWithoutAProtocolIsRefused(t *testing.T) {
|
||||
m := Manifest{Module: "vague", Seats: []SeatDeclaration{{Name: "something", Scope: ScopeMesh}}}
|
||||
if got := strings.Join(declaredSeatProblems(m), "; "); !strings.Contains(got, "no protocol") {
|
||||
t.Fatalf("a seat promising nothing was accepted: %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) {
|
||||
m := Manifest{Module: "nats", 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user