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
146 lines
5.3 KiB
Go
146 lines
5.3 KiB
Go
package views
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
type swFinding struct {
|
|
Kind string `json:"kind"`
|
|
Scope string `json:"scope"`
|
|
ID string `json:"id"`
|
|
Observer string `json:"observer"`
|
|
At string `json:"at"`
|
|
Since string `json:"since"`
|
|
Subject struct {
|
|
Scope string `json:"scope"`
|
|
} `json:"subject"`
|
|
}
|
|
type swCleared struct {
|
|
Kind string `json:"kind"`
|
|
Scope string `json:"scope"`
|
|
ID string `json:"id"`
|
|
At string `json:"at"`
|
|
Why string `json:"why"`
|
|
}
|
|
type swStated struct {
|
|
At string `json:"at"`
|
|
Findings []swFinding `json:"findings"`
|
|
Pending []PendingKey `json:"pending"`
|
|
}
|
|
type swAsked struct {
|
|
Ask string `json:"ask"`
|
|
}
|
|
|
|
// A stdio module's writer says each of its events through the module's own publish, acknowledged, under the writer's
|
|
// message id, and hears the asks through the module's subscription (design 47 G A-16).
|
|
func subStdioWriterPublishesAndListensThroughTheModulesOwnPlumbing(t *testing.T) {
|
|
type said struct {
|
|
event string
|
|
body string
|
|
o stdio.EmitOptions
|
|
}
|
|
var out []said
|
|
var subscribed []string
|
|
var deliver func(stdio.Envelope) error
|
|
w, err := newStdioWriter(WriterConfig[swFinding, swCleared, swStated, swAsked]{
|
|
Module: "m", Node: "n1", State: nil, Asked: "mesh.seat.s.event.findings-asked",
|
|
Observe: func(context.Context) (Observed[swFinding], error) { return Observed[swFinding]{}, nil },
|
|
}, func(event string, body any, o ...stdio.EmitOptions) error {
|
|
raw, _ := json.Marshal(body)
|
|
out = append(out, said{event, string(raw), o[0]})
|
|
return nil
|
|
}, func(pattern string, h func(stdio.Envelope) error) error {
|
|
subscribed = append(subscribed, pattern)
|
|
deliver = h
|
|
return nil
|
|
})
|
|
if err == nil || !strings.Contains(err.Error(), "state") {
|
|
t.Fatalf("the writer still needs its state: %v", err)
|
|
}
|
|
w, err = newStdioWriter(WriterConfig[swFinding, swCleared, swStated, swAsked]{
|
|
Module: "m", Node: "n1", State: noState{}, Asked: "mesh.seat.s.event.findings-asked",
|
|
Observe: func(context.Context) (Observed[swFinding], error) { return Observed[swFinding]{}, nil },
|
|
}, func(event string, body any, o ...stdio.EmitOptions) error {
|
|
raw, _ := json.Marshal(body)
|
|
out = append(out, said{event, string(raw), o[0]})
|
|
return nil
|
|
}, func(pattern string, h func(stdio.Envelope) error) error {
|
|
subscribed = append(subscribed, pattern)
|
|
deliver = h
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := w.cfg.Publish(context.Background(), w.subj.Written, []byte(`{"a":1}`), "id-1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(out) != 1 || out[0].event != "finding-written" || out[0].body != `{"a":1}` || out[0].o.MessageID != "id-1" || !out[0].o.Ack {
|
|
t.Fatalf("the publish: %+v", out)
|
|
}
|
|
var heard []string
|
|
stop, err := w.cfg.Listen(context.Background(), w.cfg.Asked, func(b []byte) { heard = append(heard, string(b)) })
|
|
if err != nil || len(subscribed) != 1 || subscribed[0] != "s.findings-asked" {
|
|
t.Fatalf("the listen: %v %v", subscribed, err)
|
|
}
|
|
_ = deliver(stdio.Envelope{Body: json.RawMessage(`{"ask":"x"}`)})
|
|
stop()
|
|
_ = deliver(stdio.Envelope{Body: json.RawMessage(`{"ask":"y"}`)})
|
|
if len(heard) != 1 || heard[0] != `{"ask":"x"}` {
|
|
t.Fatalf("heard %v: a stopped listener hands on nothing", heard)
|
|
}
|
|
if err := w.cfg.Publish(context.Background(), "obs.event", nil, ""); err == nil {
|
|
t.Fatal("a subject of no event form is refused")
|
|
}
|
|
// the held form fills the same plumbing and states what it holds (design 47 G A-37)
|
|
out = nil
|
|
h, err := NewHeldWriter(withStdioPlumbing(WriterConfig[swFinding, swCleared, swStated, swAsked]{
|
|
Module: "m", Node: "n1", State: noState{}, Asked: "mesh.seat.s.event.findings-asked",
|
|
}, func(event string, body any, o ...stdio.EmitOptions) error {
|
|
raw, _ := json.Marshal(body)
|
|
out = append(out, said{event, string(raw), o[0]})
|
|
return nil
|
|
}, func(string, func(stdio.Envelope) error) error { return nil }))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := h.Write(context.Background(), swFinding{Kind: "k", Scope: "module", ID: "x", Observer: "m", Subject: struct {
|
|
Scope string `json:"scope"`
|
|
}{"module"}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := h.State(context.Background(), "a"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(out) == 0 || out[len(out)-1].event != "findings-stated" || !strings.Contains(out[len(out)-1].body, `"id":"x"`) {
|
|
t.Fatalf("a stdio held writer states its findings: %+v", out)
|
|
}
|
|
}
|
|
|
|
type noState struct{}
|
|
|
|
func (noState) Get(string) (*stdio.StateEntry, error) { return nil, nil }
|
|
func (noState) Put(string, any) (uint64, error) { return 0, nil }
|
|
func (noState) Delete(string) error { return nil }
|
|
func (noState) Keys() ([]string, error) { return nil, nil }
|
|
|
|
// Design 47 G A-274: the event of a writer's subject is read in both forms, the old stream's and the binding's.
|
|
func TestEventOfSubjectReadsBothForms(t *testing.T) {
|
|
for subject, want := range map[string]string{
|
|
"mesh.stream.m.event.finding-written.n1": "finding-written",
|
|
"mesh.bus.m.finding-events.findings-stated.n1": "findings-stated",
|
|
} {
|
|
if got, err := eventOfSubject(subject); err != nil || got != want {
|
|
t.Errorf("%s: %q, %v, want %q", subject, got, err, want)
|
|
}
|
|
}
|
|
if _, err := eventOfSubject("mesh.bus.m.other.finding-written.n1"); err == nil {
|
|
t.Error("another binding's subject was read as a finding event")
|
|
}
|
|
}
|