Files
mesh-catalog/modules/mesh-delivery/cmd/mesh-delivery/manifest_test.go
T

178 lines
6.5 KiB
Go

package main
import (
"encoding/json"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"time"
)
// The manifest says what the code does: the seat it claims and the verbs it serves, the events it follows
// and says, the tools it calls, its state — and names nothing of one installation.
type manifest struct {
Module string `json:"module"`
Claims []struct {
Name string `json:"name"`
Scope string `json:"scope"`
Serves []string `json:"serves"`
} `json:"claims"`
Seats []any `json:"seats"`
Uses []string `json:"uses"`
Consumes []string `json:"consumes"`
Emits []string `json:"emits"`
Invokes []string `json:"invokes"`
State []string `json:"state"`
Tools []string `json:"tools"`
}
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 TestItClaimsTheDeliverySeatAndFollowsWhatItHandles(t *testing.T) {
m, raw := readManifest(t)
if m.Module != "mesh-delivery" || len(m.Claims) != 1 || m.Claims[0].Name != "mesh-delivery" || m.Claims[0].Scope != "mesh" {
t.Fatalf("claims %+v", m.Claims)
}
// The seat is the mesh's own (mesh-*): a module claims it and never declares it.
if len(m.Seats) != 0 {
t.Fatal("the manifest declares a seat; mesh-delivery is the mesh's own")
}
if !reflect.DeepEqual(m.Consumes, followed) {
t.Fatalf("consumes %v, follows %v", m.Consumes, followed)
}
if !reflect.DeepEqual(m.Emits, []string{"transition", "group", "merged"}) {
t.Fatalf("emits %v", m.Emits)
}
if !reflect.DeepEqual(m.State, []string{"deliveries", "groups", "asked", "pulls"}) {
t.Fatalf("state %v", m.State)
}
// It calls exactly what its ports ask: the controller's verbs and the forge's tools.
want := []string{}
for _, v := range []string{"delivery-plan", "delivery-check", "deliver", "delivery-backups-again", "delivery-stop", "delivery-walks", "forget-checks"} {
want = append(want, "seat:"+ControllerSeat+"."+v)
}
want = append(want, "gitea.gitea_note_append", "gitea.gitea_delivery_view", "gitea.gitea_commit_status",
"gitea.gitea_commit_statuses", "gitea.gitea_contains", "gitea.gitea_open_pulls",
// The merge on a ready mark (novox/hq issue 722): the merge tool, a comment, a label taken off.
"gitea.gitea_merge_pull_request", "gitea.gitea_add_comment")
// A merge into main asks the operator for the Approve (novox/hq ADR 0398 §3, issue 948), as the knowledge base asks.
want = append(want, "seat:operator-channel.notify")
if !reflect.DeepEqual(m.Uses, []string{"operator-channel"}) {
t.Fatalf("uses %v", m.Uses)
}
if !reflect.DeepEqual(m.Invokes, want) {
t.Fatalf("invokes %v, wanted %v", m.Invokes, want)
}
for _, never := range []string{"/home/", "jochen", "g14", "shanks", "novox", "zurag", "anchor"} {
if strings.Contains(strings.ToLower(raw), never) {
t.Errorf("module.json names %q", never)
}
}
}
func TestTheToolsAgreeWithTheManifest(t *testing.T) {
m, _ := 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 seat, verb, ok := strings.Cut(tool.Name, "."); ok {
if seat != "mesh-delivery" {
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)
}
sort.Strings(own)
if !reflect.DeepEqual(own, m.Tools) {
t.Fatalf("serves %v, lists %v", own, m.Tools)
}
if !reflect.DeepEqual(verbs, m.Claims[0].Serves) {
t.Fatalf("verbs %v, claim %v", verbs, m.Claims[0].Serves)
}
}
// The forge's and the controller's answers are read whatever wraps them.
func TestAnAnswerIsReadWhateverWrapsIt(t *testing.T) {
for _, c := range []struct {
raw string
ok bool
answer string
}{
{`{"output":"x","ok":true,"answer":{"held":true}}`, true, `{"held":true}`},
{`{"output":"refused: no plan","ok":false}`, false, ``},
{`"{\"output\":\"x\",\"ok\":true,\"answer\":[1]}"`, true, `[1]`},
{`{"content":[{"type":"text","text":"{\"output\":\"x\",\"ok\":true,\"answer\":2}"}]}`, true, `2`},
{`{"content":[{"type":"text","text":"boom"}],"isError":true}`, false, ``},
} {
answer, _, ok := answerOf(json.RawMessage(c.raw))
if ok != c.ok || (c.ok && string(answer) != c.answer) {
t.Errorf("%s → %s %v", c.raw, answer, ok)
}
}
}
// **No ready mark is left in the module** (novox/hq ADR 0421 §4, issue 1022; how it is checked): no label, comment, verb
// or trusted setting of the mark stands in any of its code or its manifest. Tests are left out: they name the words to
// prove them gone.
func TestNoReadyMarkIsLeftInTheModule(t *testing.T) {
forbidden := []string{"mesh/ready", "ready at", "ready-authors", "mark-ready", "ReadyLabel", "pull-request.labeled",
"pull-request.unlabeled", "pull-request.commented"}
files, err := filepath.Glob("*.go")
if err != nil {
t.Fatal(err)
}
files = append(files, filepath.Join("..", "..", "module.json"))
for _, f := range files {
if strings.HasSuffix(f, "_test.go") {
continue
}
raw, err := os.ReadFile(f)
if err != nil {
t.Fatal(err)
}
for _, word := range forbidden {
if strings.Contains(string(raw), word) {
t.Errorf("%s still holds %q: the ready mark is gone (ADR 0421 §4)", f, word)
}
}
}
}
// A row written while the ready mark stood still reads: its mark's fields are ignored, its ask and merge kept (ADR 0421
// §4 consequences, issue 1022).
func TestARowWrittenWithTheReadyMarkStillReads(t *testing.T) {
store := newMemStore()
row := `{"id":"novox/app@111111111111","repository":"novox/app","commit":"` + readyHead + `","number":7,"base":"main",
"state":"ready","since":"2026-10-17T10:00:00Z","created":"2026-10-17T10:00:00Z","let_go":"0001-01-01T00:00:00Z",
"ready":{"head":"` + readyHead + `","by":"lead@shanks","marked_at":"2026-10-17T10:01:00Z","seen":"2026-10-17T10:02:00Z",
"last_try":"0001-01-01T00:00:00Z","asked_at":"2026-10-17T10:03:00Z","ask_id":"merge-7-111111111111-1","heads":{"novox/app@111111111111":"` + readyHead + `"}}}`
store.deliveries["novox/app@111111111111"] = []byte(row)
w := newWorldOver(t, store, newFakeController(), newFakeForge(), time.Date(2026, 10, 17, 11, 0, 0, 0, time.UTC))
d := w.delivery("novox/app@111111111111")
if d.Ready == nil || d.Ready.AskID != "merge-7-111111111111-1" || d.Ready.Head != readyHead {
t.Fatalf("the old row is not read: %+v", d.Ready)
}
if !MemberOf(d).Ready {
t.Fatalf("an ask waiting is a ready member")
}
}