Main reverted G's half merge (#189, reverted by8afc3ebin #191) and then took the reader release's S1-S5, which copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's headf30dee54only by those and the release's two tests (objects cap option, seat states and stream kinds). Builds: design 47 G (the milestone's sdk part, as atf30dee54) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
60 lines
2.4 KiB
Go
60 lines
2.4 KiB
Go
package process
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
)
|
|
|
|
func TestTheHubMatchesSubjectsAndRefusesOverlappingPatterns(t *testing.T) {
|
|
for _, c := range []struct {
|
|
a, b string
|
|
match, overlap bool
|
|
}{
|
|
{"mesh.mod.*.event.views.*", "mesh.mod.o.event.views.x", true, true},
|
|
{"mesh.mod.p.event.>", "mesh.mod.p.event", false, false},
|
|
{"mesh.mod.p.event.a", "mesh.mod.q.event.a", false, false},
|
|
{"mesh.mod.p.event.>", "mesh.mod.*.event.a", false, true},
|
|
{"mesh.mod.p.event.a", "mesh.mod.p.event.a.b", false, false},
|
|
} {
|
|
if got := subjectMatches(c.a, c.b); got != c.match && c.match {
|
|
t.Errorf("%s should match %s", c.a, c.b)
|
|
}
|
|
if got := subjectsOverlap(c.a, c.b); got != c.overlap {
|
|
t.Errorf("overlap(%s, %s) = %v", c.a, c.b, got)
|
|
}
|
|
}
|
|
h := NewEventHub(nil)
|
|
nop := func(jetstream.Msg) {}
|
|
if err := h.Handle("mesh.mod.p.event.>", nop); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := h.Handle("mesh.mod.*.event.a", nop); err == nil {
|
|
t.Fatal("an overlapping pattern was accepted")
|
|
}
|
|
if err := h.Handle("mesh.mod.q.event.>", nop); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// W1.12: a seat's event kept in a stream of its own is heard on one bus name and one durable, the maker's and the
|
|
// hearer's alike; an event kept last per subject is delivered from the last of each subject. The name is the
|
|
// contract's Stream<E> as it stands (seatdef.KeptStream), never made again here: a dotted or unprefixed name is
|
|
// refused by name before the bus is asked.
|
|
a := Account{Node: "ace", Module: "mesh-grants"}
|
|
for _, stream := range []string{"node-engine.running", "SEAT_node-engine.running", "SEAT_", "EVENTS"} {
|
|
if _, err := a.HearSeatEvents(context.Background(), nil, stream, func(jetstream.Msg) {}, nil); err == nil ||
|
|
!strings.Contains(err.Error(), "is not the bus's name of a seat event's stream") {
|
|
t.Errorf("hearing %q: %v", stream, err)
|
|
}
|
|
}
|
|
cfg := a.SeatEventConsumerConfig([]string{"mesh.seat.node-engine.event.running.>"}, true)
|
|
if cfg.Durable != "ace_mesh-grants" || cfg.DeliverPolicy != jetstream.DeliverLastPerSubjectPolicy ||
|
|
cfg.AckPolicy != jetstream.AckExplicitPolicy || len(cfg.FilterSubjects) != 1 {
|
|
t.Errorf("the durable on a stream kept last per subject: %+v", cfg)
|
|
}
|
|
if cfg := a.SeatEventConsumerConfig(nil, false); cfg.DeliverPolicy != jetstream.DeliverAllPolicy {
|
|
t.Errorf("the durable on a stream kept with an age starts from its first message, got %v", cfg.DeliverPolicy)
|
|
}
|
|
}
|