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 }