Files
mesh-catalog/modules/messenger/cmd/messenger/manifest_test.go
T
jochen 82475691b2 issue 797: messenger gains the tell verb for a plain notice
ADR 0380 §8: the operator channel's notify is shaped for conditions (a key that
stays open, escalation, a plain rule that rewrites a subject), so a module has
no way to say a plain notice such as new mail. tell sends a headline and one or
two lines at once to every channel, bypassing routing, the burst window and the
walk hold; it offers Details like any message, is kept in the history, but its
record is marked cleared at once so it never stays open. Its cap (10 a channel
an hour) is its own, apart from notify's and the conditions'.
2026-10-15 17:37:15 +02:00

143 lines
4.9 KiB
Go

package main
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/asks"
)
// The manifest says what the code does (novox/hq ADR 0259): the seats it declares — the operator's, and
// the two kinded benches its channels hold — the one it holds and the verbs it serves, the bench it sends
// on, the events it consumes, the one it emits, the tool it calls, its state, and its own tools. It holds
// no channel's secret, and names nothing of one installation.
type manifest struct {
Module string `json:"module"`
Seats []seat `json:"seats"`
Claims []seat `json:"claims"`
Uses []string
Consumes []string
Emits []string
Invokes []string
State []json.RawMessage
Own map[string]string `json:"own-secrets"`
RunsAs string `json:"runs-as"`
SecretsOwner string `json:"secrets-owner"`
Tools []string
Build struct {
Artifacts []map[string]any `json:"artifacts"`
} `json:"build"`
}
type seat struct {
Name string `json:"name"`
Scope string `json:"scope"`
Serves []string `json:"serves"`
Accepts []string `json:"accepts"`
Emits []string `json:"emits"`
Kinded bool `json:"kinded"`
ByCaller []string `json:"by-caller"`
Proofs []string `json:"proofs"`
Records []string `json:"records"`
}
func readManifest(t *testing.T) (manifest, string) {
t.Helper()
raw, err := os.ReadFile(filepath.Join("..", "..", "module.json"))
if err != nil {
t.Fatal(err)
}
var m manifest
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
return m, string(raw)
}
func TestItDeclaresTheSeatsOfAnAskAndHoldsTheOperatorChannel(t *testing.T) {
m, _ := readManifest(t)
verbs := []string{"open", "history", "notify", "tell", "asks"}
want := []seat{
{Name: asks.Seat, Scope: "mesh", Serves: verbs, Accepts: []string{"ask", "cancel"}, Emits: []string{"decided"},
ByCaller: []string{"ask", "cancel", "decided"}, Records: []string{"asks"}},
{Name: asks.ChannelSeat, Scope: "mesh", Kinded: true, Accepts: []string{asks.Show, asks.Edit, asks.Send}},
{Name: asks.IntakeSeat, Scope: "mesh", Kinded: true,
Emits: []string{asks.Chosen, asks.Link, asks.FailedWhat, asks.StandingWhat}, Proofs: []string{asks.CodeProof}},
}
if !reflect.DeepEqual(m.Seats, want) {
t.Fatalf("seats %+v", m.Seats)
}
if len(m.Claims) != 1 || m.Claims[0].Name != asks.Seat || !reflect.DeepEqual(m.Claims[0].Serves, verbs) {
t.Fatalf("claims %+v", m.Claims)
}
if !reflect.DeepEqual(m.Uses, []string{asks.ChannelSeat}) {
t.Fatalf("uses %v", m.Uses)
}
if !reflect.DeepEqual(m.Consumes, []string{
ControllerSeat + "." + EventRaised, ControllerSeat + "." + EventChanged, ControllerSeat + "." + EventCleared,
ControllerSeat + "." + EventPlanMoved, "intake.choice", "intake.link", "intake.failed", "intake.standing"}) {
t.Fatalf("consumes %v", m.Consumes)
}
// It performs nothing: the verbs it calls read the open conditions, whether root is free, and which issues
// track which conditions.
if !reflect.DeepEqual(m.Emits, []string{"refused"}) || !reflect.DeepEqual(m.Invokes, []string{"seat:" + ControllerSeat + ".conditions",
"seat:" + ControllerSeat + ".root-free", TrackingVerb}) {
t.Fatalf("emits %v invokes %v", m.Emits, m.Invokes)
}
var state []string
for _, s := range m.State {
var name string
if json.Unmarshal(s, &name) != nil {
var full struct{ Name string }
_ = json.Unmarshal(s, &full)
name = full.Name
}
state = append(state, name)
}
if !reflect.DeepEqual(state, []string{"open", "sent", "asks", "identities", "walks"}) {
t.Fatalf("state %v", state)
}
// It holds no channel's secret: only its own bus account, read by its own account alone (ADR 0259 §8).
if len(m.Own) != 1 || m.Own["broker"] == "" || m.RunsAs != "messenger" || m.SecretsOwner != "messenger" {
t.Fatalf("own secrets %v, runs as %q, secrets owned by %q", m.Own, m.RunsAs, m.SecretsOwner)
}
}
func TestTheToolsAgreeWithTheManifest(t *testing.T) {
m, raw := readManifest(t)
var own, verbs []string
for _, tool := range tools(&Holder{}, &listening{}) {
if strings.TrimSpace(tool.Description) == "" {
t.Errorf("%s has no description", tool.Name)
}
if seatName, verb, ok := strings.Cut(tool.Name, "."); ok {
if seatName != "operator-channel" {
t.Errorf("%s is a verb of a seat this does not hold", tool.Name)
}
verbs = append(verbs, verb)
continue
}
own = append(own, tool.Name)
}
listed := append([]string(nil), m.Tools...)
sort.Strings(own)
sort.Strings(listed)
if !reflect.DeepEqual(own, listed) {
t.Fatalf("serves %v, lists %v", own, listed)
}
if !reflect.DeepEqual(verbs, m.Claims[0].Serves) {
t.Fatalf("verbs %v, claim %v", verbs, m.Claims[0].Serves)
}
for _, never := range []string{"/home/", "jochen", "g14", "shanks", "novox", "zurag", "telegram"} {
if strings.Contains(strings.ToLower(raw), never) {
t.Errorf("module.json names %q", never)
}
}
}