163 lines
4.6 KiB
Go
163 lines
4.6 KiB
Go
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)
|
|
}
|
|
}
|