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) }