119 lines
5.3 KiB
Go
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)
|
|
}
|
|
}
|