diff --git a/elder_test.go b/elder_test.go new file mode 100644 index 0000000..2325ff2 --- /dev/null +++ b/elder_test.go @@ -0,0 +1,91 @@ +package main + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" +) + +// echoBrain returns the prompt itself, so an Elder merge contains the reports. +type echoBrain struct{} + +func (echoBrain) Name() string { return "echo" } +func (echoBrain) Ask(ctx context.Context, system, prompt string) (string, int, error) { + return prompt, 10, nil +} + +func newEchoServer(t *testing.T) (*httptest.Server, *Server) { + t.Helper() + ts, s := newTestNestServer(t) + s.p.Flock.brain = echoBrain{} + return ts, s +} + +func TestElderAddressableInNest(t *testing.T) { + ts, s := newEchoServer(t) + id := newNest(t, ts, "Lab", `"Hugin","Munin"`) + r := postRequest(t, ts, id, "conspiracy", "is this plan risky") + postDeliver(t, ts, id, "Hugin", r, "the mint Condition is fragile") + postDeliver(t, ts, id, "Munin", r, "the audit trail says otherwise") + + code, _ := postMsg(t, ts, id, `{"author":"owner","kind":"request","to":"Elder","text":"merge this","refers_to":"`+r+`"}`) + if code != http.StatusCreated { + t.Fatalf("elder request: %d", code) + } + + elders := awaitMsgsBy(t, ts, id, "Elder") + if len(elders) != 1 { + t.Fatalf("want exactly one Elder message, got %v", elders) + } + m := elders[0] + text, _ := m["text"].(string) + for _, want := range []string{"fragile", "otherwise"} { + if !strings.Contains(text, want) { + t.Fatalf("Elder merge must carry both reports (missing %q): %q", want, text) + } + } + if m["refers_to"] != r || m["to"] != "owner" || m["kind"] != "deliver" { + t.Fatalf("Elder reply shape wrong: %v", m) + } + _ = s +} + +func TestElderOnlyMergesOnRequest(t *testing.T) { + ts, s := newEchoServer(t) + id := newNest(t, ts, "Lab", `"Hugin","Munin"`) + r := postRequest(t, ts, id, "conspiracy", "is this plan risky") + postDeliver(t, ts, id, "Hugin", r, "ship it") + postDeliver(t, ts, id, "Munin", r, "ship it too") + time.Sleep(300 * time.Millisecond) + if got := awaitMsgsBy(t, ts, id, "Elder"); len(got) != 0 { + t.Fatalf("Elder must stay silent unless asked, got %v", got) + } + _ = s +} + +func TestParseAddressingServiceRole(t *testing.T) { + ts, s := newTestNestServer(t) + id := newNest(t, ts, "Lab", `"Hugin"`) + if got := parseAddressing("please @Elder merge this", nestMembers(nestOf(s, id))); got != "Elder" { + t.Fatalf("Elder must be an addressable service handle, got %q", got) + } +} +func awaitMsgsBy(t *testing.T, ts *httptest.Server, id, author string) []map[string]any { + t.Helper() + deadline := time.Now().Add(2 * time.Second) + for { + var out []map[string]any + for _, m := range nestMessages(t, ts, id) { + if m["author"] == author { + out = append(out, m) + } + } + if len(out) > 0 || time.Now().After(deadline) { + return out + } + time.Sleep(10 * time.Millisecond) + } +}