Files
mesh-delivery/modules/mesh-delivery-indexer/takein/takein_test.go
T
jschoubben c78941e80b
mesh/delivery merged: source.main moved to novox/mesh-delivery@4596fbe1b7ac
mesh/delivery-group group feat/take-in merged: every member is merged
mesh/check being carried to c78941e80b59 on 4596fbe1b7ac | from ae31b66a9e33, mesh-catalog fd4931a75261 | novox/mesh-delivery#153 in group feat/take-in
Take-in events are the indexer's ordinary events: no stream of its own, which the manifest validator refuses (issue 1585, ADR 0633 §5)
The group's check failed at 7b7096f3: the group's own manifest validator refuses the indexer's stream "take-in", outside
the bus's predefined forms. The lead's ruling: the indexer says take-in.pending and take-in.taken as its ordinary events
(its emits, mesh.mod.<module>.event.take-in.<kind>), as manifest-accepted is said, and keeps no stream. The wait's key is
in the body only: an emitted event names one event (no wildcard), and a key such as g.<group> is no event name. The
watchman consumes the two events by name on the shared events stream; it needs no replay, since it seeds its waits from
indexer_pending at its start. indexer_pending says the last event of each wait as take-in.<kind>.

Run locally, as the check runs it: cmd/mesh-judge over every manifest of mesh-catalog, mesh-delivery, mesh-host,
mesh-tools, mesh-networking and mesh-media-catalog, with this branch's and mesh-catalog's feat/take-in manifests in the
change case and origin/main's in the trunk case: 171 of 171 accepted in both. The same run with the indexer's manifest at
7b7096f3 refuses it with the check's words.

Claude-Session: https://claude.ai/code/session_01JG73KG7AfSdh6FP8VrBqCn
2026-11-05 00:59:27 +01:00

97 lines
4.6 KiB
Go

package takein
import (
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// sampleTakeIns are a pending revision, a group being gathered and a hand take-in's taken (1585/T2).
func sampleTakeIns(since time.Time) []TakeIn {
m := []Merge{{Repository: "novox/mesh-delivery", Number: 9, Merge: "eeee"}}
return []TakeIn{
{Kind: TakeInPending, Key: PendingKey(12), What: TakeInRevision, Merges: m, WaitsFor: "src2:abc", Since: since},
{Kind: TakeInPending, Key: GroupTakeInKey("feat/take-in"), What: TakeInGroup, Merges: m, Group: "feat/take-in",
Members: []delivery.Head{{Repository: "novox/mesh-delivery", Number: 9, Head: "ffff"}, {Repository: "novox/mesh-sdk", Number: 37, Head: "dddd"}}, Since: since},
{Kind: TakeInTaken, Key: PendingKey(12), What: TakeInRevision, Merges: m, WaitsFor: "src2:abc", Since: since,
By: "lead-47@shanks", Why: "the named indexer can never run"},
}
}
// TestTakeInEvents holds the indexer's take-in contract, moved from mesh-sdk go/delivery with its tests (1585/T2, T12).
func TestTakeInEvents(t *testing.T) {
// The take-in events, `take-in.<pending|taken>`; a group's key with its dots and no subject wildcard, in the body.
if PendingKey(42) != "m.00000000000000000042" || GroupTakeInKey("feat/a.b *>") != "g.feat/a.b___" {
t.Errorf("take-in keys %q %q", PendingKey(42), GroupTakeInKey("feat/a.b *>"))
}
for kind, want := range map[string]string{TakeInPending: TakeInPendingEvent, TakeInTaken: TakeInTakenEvent} {
ev, err := TakeInEvent(kind)
if err != nil || ev != want || ev != "take-in."+kind {
t.Fatalf("take-in event = %q, %v", ev, err)
}
if k, err := ParseTakeInEvent(ev); err != nil || k != kind {
t.Errorf("parse take-in event %q = %q %v", ev, k, err)
}
}
for _, bad := range []string{"", "take-in", "take-in.held", "take-in.pending.m.1", "forge.taken"} {
if _, err := ParseTakeInEvent(bad); err == nil {
t.Errorf("parse take-in event %q accepted", bad)
}
}
// The bodies round-trip: a pending revision, a group being gathered, and a hand take-in's taken with its by.
since := time.Date(2026, 11, 4, 23, 0, 0, 0, time.UTC)
for _, ti := range sampleTakeIns(since) {
data, err := EncodeTakeIn(ti)
if err != nil {
t.Fatalf("take-in %s %s: %v", ti.Kind, ti.What, err)
}
ti.V = delivery.Version
if got, err := DecodeTakeIn(data); err != nil || !SameTakeIn(got, ti) {
t.Errorf("take-in %s round trip: %+v %v", ti.Key, got, err)
}
}
for _, in := range []string{`{"key":"m.1"}`, `{"v":2,"key":"m.1"}`} {
if _, err := DecodeTakeIn([]byte(in)); !errors.Is(err, delivery.ErrUnknownVersion) {
t.Errorf("decode of %s = %v, want delivery.ErrUnknownVersion", in, err)
}
}
// A body that does not hold is refused, naming why.
for _, c := range []struct {
name, want string
i int
mut func(*TakeIn)
}{
{"group take-in without its group", "names its group", 1, func(t *TakeIn) { t.Group = "" }},
{"group take-in under another group's key", "g.other", 1, func(t *TakeIn) { t.Key = GroupTakeInKey("other") }},
{"pending revision waiting for no build", "waits for", 0, func(t *TakeIn) { t.WaitsFor = "" }},
{"pending with a hand act", "only a taken event", 0, func(t *TakeIn) { t.By = "someone@node" }},
{"hand act without its by", "by", 2, func(t *TakeIn) { t.By = "" }},
{"take-in without merges", "no merge", 0, func(t *TakeIn) { t.Merges = nil }},
{"take-in without since", "since", 0, func(t *TakeIn) { t.Since = time.Time{} }},
{"a merge of no repository", "owner", 0, func(t *TakeIn) { t.Merges = []Merge{{Repository: "nope", Merge: "m"}} }},
{"a key that is neither a merge's nor a group's", "neither", 0, func(t *TakeIn) { t.Key = "x.1" }},
} {
ti := sampleTakeIns(since)[c.i]
c.mut(&ti)
if _, err := EncodeTakeIn(ti); err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("%s: %v, want a refusal naming %q", c.name, err, c.want)
}
}
// indexer_pending's answer reads back; a wait of neither kind is refused.
pa := PendingAnswer{Waits: []PendingWait{{Key: PendingKey(12), What: TakeInRevision, Merges: sampleTakeIns(since)[0].Merges,
Keys: []string{PendingKey(12)}, WaitsFor: "src2:abc", Since: since, Holds: []string{"mesh-delivery-indexer"}, Said: "take-in.pending." + PendingKey(12), SaidAt: since}}}
if data, err := json.Marshal(pa); err != nil {
t.Fatal(err)
} else if got, err := DecodePendingAnswer(data); err != nil || !reflect.DeepEqual(got, pa) {
t.Errorf("pending answer round trip: %+v %v", got, err)
}
if _, err := DecodePendingAnswer([]byte(`{"waits":[{"key":"m.1","what":"held"}]}`)); err == nil {
t.Error("a pending answer with a wait of neither kind accepted")
}
}