Files
so-many-crows/divergence_test.go

132 lines
3.9 KiB
Go

package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
)
// fakeNoulJev answers every question in a request with a fixed noul probability.
func fakeNoulJev(t *testing.T, noul float64) *JevClient {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var body struct {
Questions map[string]any `json:"questions"`
}
json.NewDecoder(r.Body).Decode(&body)
out := map[string]any{}
for q := range body.Questions {
out[q] = map[string]any{"type": "noul", "noul": noul}
}
json.NewEncoder(w).Encode(map[string]any{"model": "jev-1.14.0", "answers": out,
"usage": map[string]int{"input_tokens": 40, "output_tokens": 0}})
}))
t.Cleanup(srv.Close)
return &JevClient{url: srv.URL, key: "tk", model: "jev-latest", threshold: 0.7, client: srv.Client()}
}
func postDeliver(t *testing.T, ts *httptest.Server, id, author, refersTo, text string) string {
t.Helper()
code, out := post(t, ts.URL+"/api/nests/"+id+"/messages",
`{"author":"`+author+`","kind":"deliver","refers_to":"`+refersTo+`","text":"`+text+`"}`)
if code != http.StatusCreated {
t.Fatalf("deliver %s: %d %v", author, code, out)
}
return out["msg_id"].(string)
}
func findNudge(ts *httptest.Server, id, target string) map[string]any {
for _, m := range nestMessagesNoT(ts, id) {
if m["kind"] == "nudge" && m["refers_to"] == target {
return m
}
}
return nil
}
func nestMessagesNoT(ts *httptest.Server, id string) []map[string]any {
resp, err := http.Get(ts.URL + "/api/nests/" + id + "/messages")
if err != nil {
return nil
}
defer resp.Body.Close()
var msgs []map[string]any
json.NewDecoder(resp.Body).Decode(&msgs)
return msgs
}
func waitForNudge(ts *httptest.Server, id, target string) map[string]any {
deadline := time.Now().Add(2 * time.Second)
for {
if n := findNudge(ts, id, target); n != nil {
return n
}
if time.Now().After(deadline) {
return nil
}
time.Sleep(10 * time.Millisecond)
}
}
func TestRestatementGetsNudge(t *testing.T) {
ts, s := newTestNestServer(t)
s.p.Flock.SetClassifier(fakeNoulJev(t, 0.8)) // above band 0.7
id := newNest(t, ts, "Lab", `"Hugin","Munin"`)
r := postRequest(t, ts, id, "conspiracy", "is this risky")
postDeliver(t, ts, id, "Hugin", r, "ship it")
d2 := postDeliver(t, ts, id, "Munin", r, "also ship it")
n := waitForNudge(ts, id, d2)
if n == nil {
t.Fatalf("restated deliver must get a nudge, log: %+v", s.msgs.All(id))
}
if n["to"] != "Munin" || n["author"] != "harness" {
t.Fatalf("nudge must address the restating crow from the harness, got %v", n)
}
if !containsMsg(s.msgs.All(id), d2) {
t.Fatal("the restated deliver itself must stay in the log")
}
}
func TestDistinctDeliversNoNudge(t *testing.T) {
ts, s := newTestNestServer(t)
s.p.Flock.SetClassifier(fakeNoulJev(t, 0.2)) // below band
id := newNest(t, ts, "Lab", `"Hugin","Munin"`)
r := postRequest(t, ts, id, "conspiracy", "is this risky")
postDeliver(t, ts, id, "Hugin", r, "ship it")
d2 := postDeliver(t, ts, id, "Munin", r, "wait for the audit")
time.Sleep(300 * time.Millisecond)
if n := findNudge(ts, id, d2); n != nil {
t.Fatalf("a distinct deliver must not be nudged, got %v", n)
}
}
func TestNudgeSpendsNoModelTokens(t *testing.T) {
ts, s := newTestNestServer(t)
s.p.Flock.SetClassifier(fakeNoulJev(t, 0.8))
id := newNest(t, ts, "Lab", `"Hugin","Munin"`)
r := postRequest(t, ts, id, "conspiracy", "is this risky")
postDeliver(t, ts, id, "Hugin", r, "ship it")
d2, err := s.msgs.Append(Message{NestID: id, Kind: "deliver", Author: "Munin", Text: "ship", RefersTo: r, Timestamp: time.Now().UTC().Format(time.RFC3339)})
if err != nil {
t.Fatal(err)
}
b := &Budget{name: "nest", limit: 1}
s.checkDivergence(nestOf(s, id), d2, b)
if b.Spent() != 0 {
t.Fatalf("a review nudge must spend no model tokens, spent=%d", b.Spent())
}
}
func containsMsg(msgs []Message, msgID string) bool {
for _, m := range msgs {
if m.MsgID() == msgID {
return true
}
}
return false
}