package main import ( "context" "net/http" "net/http/httptest" "strings" "sync" "testing" ) // recordBrain stores every (system,prompt) it is asked; replies as a claim. type recordBrain struct { mu sync.Mutex pairs [][2]string } func (b *recordBrain) Name() string { return "recorder" } func (b *recordBrain) Ask(ctx context.Context, system, prompt string) (string, int, error) { b.mu.Lock() b.pairs = append(b.pairs, [2]string{system, prompt}) b.mu.Unlock() return "KIND: claim\nOn it.", 10, nil } func newRecordServer(t *testing.T) (*httptest.Server, *Server, *recordBrain) { t.Helper() ts, s := newTestNestServer(t) rb := &recordBrain{} s.p.Flock.brain = rb return ts, s, rb } func TestConspiracyInvitesDistinctLenses(t *testing.T) { ts, srv, rb := newRecordServer(t) id := newNest(t, ts, "Lab", `"Hugin","Munin","Soot"`) promote(srv, "Hugin", "Munin", "Soot") // earned the right to take open work code, req := post(t, ts.URL+"/api/nests/"+id+"/messages", `{"author":"owner","kind":"request","mode":"conspiracy","text":"is this launch risky"}`) if code != http.StatusCreated || req["mode"] != "conspiracy" { t.Fatalf("post: %d %v", code, req) } awaitMessages(t, ts, id, 4) // request + 3 lens claims rb.mu.Lock() defer rb.mu.Unlock() if len(rb.pairs) != 3 { t.Fatalf("want exactly 3 invited prompts, got %d", len(rb.pairs)) } seen := map[string]bool{} for _, pr := range rb.pairs { if !strings.Contains(pr[1], conspiracyLine) { t.Fatalf("every invited prompt must carry the lens line: %q", pr[1]) } hits := 0 for _, name := range []string{"Hugin", "Munin", "Soot"} { if strings.Contains(pr[1], "("+name+")") { hits++ } } if hits != 1 { t.Fatalf("prompt should name exactly one crow's lens, got %d in %q", hits, pr[1]) } if seen[pr[1]] { t.Fatal("two invited prompts were identical") } seen[pr[1]] = true } } func TestConspiracyInviteCapped(t *testing.T) { ts, srv, rb := newRecordServer(t) id := newNest(t, ts, "Wide", strings.Join(quoteAll(roster), ",")) promote(srv, roster...) code, out := post(t, ts.URL+"/api/nests/"+id+"/messages", `{"author":"owner","kind":"request","mode":"conspiracy","invited":4,"text":"risk"}`) if code != http.StatusCreated { t.Fatalf("post: %d %v", code, out) } awaitMessages(t, ts, id, 4) rb.mu.Lock() n := len(rb.pairs) rb.mu.Unlock() if n != 3 { // min(invited 4, MurderSize 3) t.Fatalf("want invite capped to MurderSize=3, got %d", n) } } func quoteAll(xs []string) []string { out := make([]string, len(xs)) for i, x := range xs { out[i] = `"` + x + `"` } return out }