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
94 lines
3.3 KiB
Go
94 lines
3.3 KiB
Go
package process
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
)
|
|
|
|
type bindMsg struct {
|
|
jetstream.Msg
|
|
subject string
|
|
data string
|
|
acked bool
|
|
}
|
|
|
|
func (m *bindMsg) Subject() string { return m.subject }
|
|
func (m *bindMsg) Data() []byte { return []byte(m.data) }
|
|
func (m *bindMsg) Ack() error { m.acked = true; return nil }
|
|
|
|
// design 47 G §577: a process reaches the kept data its `requires` declares through its Binding: `show` of the seat
|
|
// mesh-broker at the start, asked again on `recreated` and `dropped` of the entry, and a wait naming the entry until it answers.
|
|
func TestABindingAsksShowAndAgainOnRecreatedAndDropped(t *testing.T) {
|
|
var mu sync.Mutex
|
|
bucket, answers := "m_performed", true
|
|
var asked []string
|
|
call := func(key string, body any) (json.RawMessage, error) {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
asked = append(asked, key+" "+toJSON(body))
|
|
if !answers {
|
|
return nil, errors.New("no responders")
|
|
}
|
|
return json.RawMessage(`{"binding":{"local":"performed","storage":"key-value","bucket":"` + bucket + `"}}`), nil
|
|
}
|
|
b := NewBinding("mesh-broker", "m", call, nil, "performed", "events")
|
|
b.spawn = func(f func()) { f() }
|
|
if _, err := b.Names("performed"); err == nil || !strings.Contains(err.Error(), `"performed"`) {
|
|
t.Fatalf("before show answered: %v", err)
|
|
}
|
|
if w := b.Waiting(); !strings.Contains(w, `"performed"`) || !strings.Contains(w, `"events"`) {
|
|
t.Fatalf("waiting words %q name neither entry", w)
|
|
}
|
|
if _, err := b.Names("nope"); err == nil {
|
|
t.Fatal("an entry this Binding was not made for was answered")
|
|
}
|
|
b.Ask("performed")
|
|
b.Ask("events")
|
|
got, err := b.Names("performed")
|
|
if err != nil || got.Bucket != "m_performed" || b.Waiting() != "" {
|
|
t.Fatalf("after show: %+v, %v, waiting %q", got, err, b.Waiting())
|
|
}
|
|
if !strings.Contains(asked[0], `seat:mesh-broker.show {"local-name":"performed","module":"m"}`) {
|
|
t.Fatalf("asked %v", asked)
|
|
}
|
|
// recreated for this module's entry: asked again, the new names kept
|
|
mu.Lock()
|
|
bucket = "m_performed2"
|
|
mu.Unlock()
|
|
for _, m := range []*bindMsg{
|
|
{subject: "mesh.seat.mesh-broker.event.recreated", data: `{"module":"other","local-name":"performed"}`},
|
|
{subject: "mesh.seat.mesh-broker.event.recreated", data: `{"module":"m","local-name":"performed"}`},
|
|
} {
|
|
b.Hear(m)
|
|
if !m.acked {
|
|
t.Errorf("%s not acknowledged", m.data)
|
|
}
|
|
}
|
|
if n, _ := b.Names("performed"); n.Bucket != "m_performed2" {
|
|
t.Fatalf("recreated was not asked again: %+v", n)
|
|
}
|
|
// dropped: unbound until show answers, which it does not
|
|
mu.Lock()
|
|
answers = false
|
|
mu.Unlock()
|
|
b.Hear(&bindMsg{subject: "mesh.seat.mesh-broker.event.dropped", data: `{"module":"m","local-name":"performed"}`})
|
|
if _, err := b.Names("performed"); err == nil || !strings.Contains(err.Error(), "no responders") {
|
|
t.Fatalf("dropped left it bound: %v", err)
|
|
}
|
|
// an entry the bus holds no objects for is waited for, not bound
|
|
b2 := NewBinding("mesh-broker", "m", func(string, any) (json.RawMessage, error) {
|
|
return json.RawMessage(`{"state":"waiting","why":"not made"}`), nil
|
|
}, nil, "x")
|
|
b2.Ask("x")
|
|
if _, err := b2.Names("x"); err == nil || !strings.Contains(err.Error(), "not made") {
|
|
t.Fatalf("an entry with no binding: %v", err)
|
|
}
|
|
}
|
|
|
|
func toJSON(v any) string { d, _ := json.Marshal(v); return string(d) }
|