test(relay): red for #40 — Quill relays owner asks as nest requests
This commit is contained in:
1 parent
4494a4ac19
commit
bfffb04a87
2 files changed
+164
No files matched your search
+162
@@ -0,0 +1,162 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// relayBrain records every (system, prompt) pair and answers with scripted
|
||||
// replies in call order, falling back to plain chatter once they run out.
|
||||
type relayBrain struct {
|
||||
mu sync.Mutex
|
||||
pairs [][2]string
|
||||
replies []string
|
||||
calls int
|
||||
}
|
||||
|
||||
func (b *relayBrain) Name() string { return "relay-rec" }
|
||||
|
||||
func (b *relayBrain) Ask(ctx context.Context, system, prompt string) (string, int, error) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.pairs = append(b.pairs, [2]string{system, prompt})
|
||||
reply := "just chatter"
|
||||
if b.calls < len(b.replies) {
|
||||
reply = b.replies[b.calls]
|
||||
}
|
||||
b.calls++
|
||||
return reply, 10, nil
|
||||
}
|
||||
|
||||
func newRelayServer(t *testing.T, replies ...string) (*httptest.Server, *Server, *relayBrain) {
|
||||
t.Helper()
|
||||
ts, s := newTestNestServer(t)
|
||||
rb := &relayBrain{replies: replies}
|
||||
s.p.Flock.brain = rb
|
||||
return ts, s, rb
|
||||
}
|
||||
|
||||
// askQuill posts the owner's request addressed to Quill and returns the
|
||||
// stored record of that message.
|
||||
func askQuill(t *testing.T, ts *httptest.Server, id, text string) map[string]any {
|
||||
t.Helper()
|
||||
code, req := post(t, ts.URL+"/api/nests/"+id+"/messages",
|
||||
`{"author":"owner","to":"Quill","kind":"request","text":"`+text+`"}`)
|
||||
if code != http.StatusCreated {
|
||||
t.Fatalf("owner request: %d %v", code, req)
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
func countBy(t *testing.T, ts *httptest.Server, id, author string, kinds ...string) (total int, matched int) {
|
||||
t.Helper()
|
||||
for _, m := range nestMessages(t, ts, id) {
|
||||
if m["author"] != author {
|
||||
continue
|
||||
}
|
||||
total++
|
||||
for _, k := range kinds {
|
||||
if m["kind"] == k {
|
||||
matched++
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestQuillRelayPostsRequest(t *testing.T) {
|
||||
ts, _, _ := newRelayServer(t,
|
||||
`RELAY {"crow":"Hugin","task":"draft the tagline"}`,
|
||||
"KIND: deliver\nSo many crows, one banner.")
|
||||
id := newNest(t, ts, "Lab", `"Hugin","Munin"`)
|
||||
req := askQuill(t, ts, id, "get Hugin to draft the tagline")
|
||||
msgs := awaitMessages(t, ts, id, 3)
|
||||
|
||||
var relay map[string]any
|
||||
for _, m := range msgs {
|
||||
if m["author"] == "Quill" && m["kind"] == "request" {
|
||||
relay = m
|
||||
}
|
||||
}
|
||||
if relay == nil {
|
||||
t.Fatalf("Quill never posted the relay request: %v", msgs)
|
||||
}
|
||||
if relay["to"] != "Hugin" {
|
||||
t.Fatalf("relay must address Hugin, got %v", relay["to"])
|
||||
}
|
||||
if relay["refers_to"] != req["msg_id"] {
|
||||
t.Fatalf("relay must refer to the owner message %v, got %v", req["msg_id"], relay["refers_to"])
|
||||
}
|
||||
if relay["text"] != "draft the tagline" {
|
||||
t.Fatalf("relay text must carry the task, got %v", relay["text"])
|
||||
}
|
||||
hugin := false
|
||||
for _, m := range msgs {
|
||||
if m["author"] == "Hugin" {
|
||||
hugin = true
|
||||
}
|
||||
}
|
||||
if !hugin {
|
||||
t.Fatalf("the addressed crow must answer the relay through must-respond: %v", msgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuillRelayUnknownCrowStalls(t *testing.T) {
|
||||
ts, _, _ := newRelayServer(t, `RELAY {"crow":"Soot","task":"blacken the roof"}`)
|
||||
id := newNest(t, ts, "Lab", `"Hugin","Munin"`)
|
||||
askQuill(t, ts, id, "get Soot to blacken the roof")
|
||||
awaitMessages(t, ts, id, 2)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
msgs := nestMessages(t, ts, id)
|
||||
|
||||
total, requests := countBy(t, ts, id, "Quill", "request")
|
||||
if total != 1 || requests != 0 {
|
||||
t.Fatalf("want exactly one Quill message and zero Quill requests, got %v in %v", msgs, total)
|
||||
}
|
||||
var stall map[string]any
|
||||
for _, m := range msgs {
|
||||
if m["author"] == "Quill" {
|
||||
stall = m
|
||||
}
|
||||
}
|
||||
if stall == nil || stall["kind"] != "say" {
|
||||
t.Fatalf("the unknown-crow reply must be a Quill say, got %v", stall)
|
||||
}
|
||||
if stall["to"] != "owner" {
|
||||
t.Fatalf("the say must go back to the requester, got %v", stall["to"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuillRelayNotAnsweredTwice(t *testing.T) {
|
||||
ts, _, _ := newRelayServer(t,
|
||||
`RELAY {"crow":"Hugin","task":"draft the tagline"}`,
|
||||
"KIND: deliver\nSo many crows, one banner.")
|
||||
id := newNest(t, ts, "Lab", `"Hugin","Munin"`)
|
||||
askQuill(t, ts, id, "get Hugin to draft the tagline")
|
||||
awaitMessages(t, ts, id, 3)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
total, delivers := countBy(t, ts, id, "Quill", "deliver")
|
||||
if total != 1 {
|
||||
t.Fatalf("a successful relay must leave Quill with exactly one message, got %d", total)
|
||||
}
|
||||
if delivers != 0 {
|
||||
t.Fatalf("a relay must not also deliver, got %d delivers", delivers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuillRelayGoesThroughFlock(t *testing.T) {
|
||||
ts, s := newTestNestServer(t)
|
||||
id := newNest(t, ts, "Lab", `"Hugin","Munin"`)
|
||||
s.p.Flock.brain = &relayBrain{replies: []string{`RELAY {"crow":"Hugin","task":"draft the tagline"}`}}
|
||||
s.p.Flock.sem = make(chan struct{}, 0)
|
||||
askQuill(t, ts, id, "get Hugin to draft the tagline")
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
if got := nestMessages(t, ts, id); len(got) != 1 {
|
||||
t.Fatalf("a saturated flock means ask fails and Quill posts nothing: %v", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user