Files
mesh-sdk/go/catalogue/verbs_test.go
T
jschoubben cf5812101b
mesh/delivery-group group feat/l3b-bus checking: 4 of 4 member(s) ready
mesh/delivery closed: a newer head of the same pull request
mesh/check checking cf5812101b0b on 2341e8e98377 | novox/mesh-sdk#165 in group feat/l3b-bus
The seat mesh-catalog's verbs' answers are its contract: CatalogModules, CatalogOne, CatalogProvides and CatalogDependents, with the field names of the catalog module's tools they replace; the module graph is no part of mesh-sdk (the operator, 2026-11-03: only contracts)
2026-11-03 16:51:54 +01:00

119 lines
5.3 KiB
Go

package catalogue
import (
"encoding/json"
"slices"
"strings"
"testing"
)
// A served verb is written as a bare name or in full, and both read into one type (novox/hq ADR 0132).
func TestAServedVerbIsANameOrADefinition(t *testing.T) {
m, err := decodeForTest([]byte(`{"module":"till","version":"1","tools":["price","refund"],` +
`"seats":[{"name":"shop-till","serves":["price",{"name":"refund","description":"give it back",` +
`"input":{"type":"object","properties":{"order":{"type":"string"}}}}]}],` +
`"claims":[{"name":"shop-till","scope":"mesh"}]}`))
if err != nil {
t.Fatal(err)
}
got := m.DefinesSeats[0].Serves
if len(got) != 2 || got[0].Name != "price" || got[1].Name != "refund" || got[1].Description != "give it back" {
t.Fatalf("verbs not read: %+v", got)
}
if got[1].Input["type"] != "object" {
t.Fatalf("the schema did not travel with the verb: %+v", got[1].Input)
}
}
// Holding a mesh seat that serves verbs requires serving them, and the refusal names the verbs.
func TestHoldingAMeshSeatRequiresServingItsVerbs(t *testing.T) {
// 774/S34/AC1: the seat mesh-enrolment is held by a module serving its six verbs, and a seventh is refused.
enrolment, ok := SeatNamed(EnrolmentSeat)
if !ok || enrolment.Scope != ScopeMesh || len(enrolment.Emits) != 5 {
t.Fatalf("mesh-enrolment: in the set %v, %+v", ok, enrolment)
}
six := []string{"issue", "redeem", "confirm", "revoke", "tokens", "again"}
// The tunnel key is given at issue (the operator, 2026-11-02 01:05).
if issue := enrolment.Serves[0]; issue.Name != "issue" || !slices.Contains(issue.Input["required"].([]string), "overlay_key") {
t.Errorf("issue requires %v", issue.Input["required"])
}
holder := func(serves ...string) Manifest {
return Manifest{Module: "mesh-enrolment", Tools: serves,
Claims: []Claim{{Name: EnrolmentSeat, Scope: ScopeMesh, Serves: serves}}}
}
if err := CanHold(holder(six...), enrolment); err != nil {
t.Errorf("the holder serving the six verbs was refused: %v", err)
}
if err := CanHold(holder(six[:5]...), enrolment); err == nil || !strings.Contains(err.Error(), "does not serve again") {
t.Errorf("a holder missing again: %v", err)
}
if err := CanHold(holder(append(six, "mint")...), enrolment); err == nil || !strings.Contains(err.Error(), "mint") {
t.Errorf("a holder saying it serves mint: %v", err)
}
// L3: the seat mesh-catalog promises four verbs, each optional while two claimants stand (ADR 0604 §1, §6).
catalog, ok := SeatNamed(CatalogSeat)
if !ok || catalog.Scope != ScopeMesh || catalog.Decision != "novox/hq ADR 0604" || len(catalog.Serves) != 4 {
t.Fatalf("mesh-catalog: in the set %v, %+v", ok, catalog)
}
four := []string{"modules", "module", "provides", "dependents"}
for i, v := range catalog.Serves {
if v.Name != four[i] || !v.Optional || v.Input == nil {
t.Errorf("mesh-catalog's verb %d is %+v, want %s, optional, with its input", i, v, four[i])
}
}
// The answers keep the field names of the catalog module's tools they replace (catalog_modules, catalog_module,
// catalog_provides, catalog_dependents, as mesh_call answered them on 2026-11-03).
for _, c := range []struct {
answer any
want []string
}{
{CatalogModules{Modules: []CatalogEntry{{}}}, []string{`"modules":[{"module":`, `"repository":`, `"path":`, `"commit":`}},
{CatalogOne{Module: CatalogModule{Manifest: json.RawMessage(`{}`)}}, []string{`"module":{"module":`, `"manifest":{}`}},
{CatalogProvides{Provides: []CatalogEnd{{Scope: "mesh"}}, Requires: []CatalogEnd{{}}}, []string{`"provides":[{"module":"","commit":"","scope":"mesh"}]`, `"requires":[{"module":"","commit":""}]`}},
{CatalogDependents{Module: "m", Dependents: []CatalogEnd{}}, []string{`"module":"m"`, `"dependents":[]`}},
} {
raw, _ := json.Marshal(c.answer)
for _, w := range c.want {
if !strings.Contains(string(raw), w) {
t.Errorf("the answer %s does not hold %s", raw, w)
}
}
}
cataloguer := func(serves ...string) Manifest {
return Manifest{Module: "a-catalog", Tools: serves, Claims: []Claim{{Name: CatalogSeat, Scope: ScopeMesh, Serves: serves}}}
}
for name, c := range map[string]struct {
serves []string
word string
}{
"serving the four": {four, ""},
"serving three, optional": {four[:3], ""},
"serving none, the old one": {nil, ""},
"serving a fifth": {append(slices.Clone(four), "rebuild"), "rebuild"},
} {
err := CanHold(cataloguer(c.serves...), catalog)
if (c.word == "") != (err == nil) || (err != nil && !strings.Contains(err.Error(), c.word)) {
t.Errorf("a mesh-catalog holder %s: %v", name, err)
}
}
was := Seats()
t.Cleanup(func() { UseSeats(was) })
UseSeats([]Seat{{Name: "mesh-controller", Scope: ScopeMesh, Decision: "test",
Serves: []Verb{{Name: "status"}, {Name: "push"}}}})
seat, _ := SeatNamed("mesh-controller")
partial := Manifest{Module: "a-controller", Tools: []string{"status"},
Claims: []Claim{{Name: "mesh-controller", Scope: ScopeMesh}}}
err := CanHold(partial, seat)
if err == nil || !strings.Contains(err.Error(), "does not serve push") {
t.Fatalf("a holder missing a verb was not refused by name: %v", err)
}
whole := Manifest{Module: "a-controller", Tools: []string{"status", "push"},
Claims: []Claim{{Name: "mesh-controller", Scope: ScopeMesh}}}
if err := CanHold(whole, seat); err != nil {
t.Fatalf("a holder serving every verb was refused: %v", err)
}
}