From 0da8cf2bb6c980fb17bcb41b46120091b025f51d Mon Sep 17 00:00:00 2001 From: Smokey <150986568+jacisjake@users.noreply.github.com> Date: Wed, 28 Oct 2026 04:59:46 -0600 Subject: [PATCH] test(nest): live-model crow delivery in the nest; ReadEvents log helper (#13) --- flightlog.go | 21 +++++++++++ nestlive_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 nestlive_test.go diff --git a/flightlog.go b/flightlog.go index 01a6033..7af2078 100644 --- a/flightlog.go +++ b/flightlog.go @@ -4,6 +4,7 @@ import ( "encoding/json" "os" "path/filepath" + "strings" "sync" "time" ) @@ -55,3 +56,23 @@ func (l *FlightLog) Write(e Event) { } func (l *FlightLog) Close() error { return l.f.Close() } + +// ReadEvents parses a flight log file back into events, in the order written. +func ReadEvents(path string) ([]Event, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var out []Event + for _, line := range strings.Split(string(data), "\n") { + if strings.TrimSpace(line) == "" { + continue + } + var e Event + if err := json.Unmarshal([]byte(line), &e); err != nil { + return nil, err + } + out = append(out, e) + } + return out, nil +} diff --git a/nestlive_test.go b/nestlive_test.go new file mode 100644 index 0000000..aab8970 --- /dev/null +++ b/nestlive_test.go @@ -0,0 +1,97 @@ +package main + +import ( + "context" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +// newLiveNestServer builds the server over the real brain from env (NewBrain) +// instead of the mock. Requires an API key; callers must skip first. +func newLiveNestServer(t *testing.T) (*Server, string) { + t.Helper() + dir := t.TempDir() + ledger, _ := LoadLedger(filepath.Join(dir, "ledger.json")) + flog, err := OpenFlightLog(filepath.Join(dir, "flight.jsonl")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { flog.Close() }) + nests, _ := LoadNests(filepath.Join(dir, "nests.json")) + msgs, _ := LoadMessageStore(filepath.Join(dir, "messages")) + ctx, cancel := context.WithCancel(context.Background()) + approver := NewWebApprover(time.Minute) + p := &Pipeline{Flock: NewFlock(NewBrain(), DefaultGuardrails(), flog), Ledger: ledger, Log: flog, G: DefaultGuardrails(), Approver: approver} + s := NewServer(ctx, p, approver, nests).WithMessages(msgs) + ts := httptest.NewServer(s.Handler()) + t.Cleanup(func() { cancel(); ts.Close() }) + return s, filepath.Join(dir, "flight.jsonl") +} + +func TestLiveCrowDeliversInNest(t *testing.T) { + if os.Getenv("OPENROUTER_API_KEY") == "" && os.Getenv("ANTHROPIC_API_KEY") == "" { + t.Skip("no model key") + } + s, logPath := newLiveNestServer(t) + nest, err := s.nests.Create("Live Lab", []string{"Hugin"}) + if err != nil { + t.Fatal(err) + } + req, err := s.msgs.Append(Message{NestID: nest.ID, Kind: "request", Author: "owner", To: "Hugin", Text: "What is the plural of crow?", Timestamp: time.Now().UTC().Format(time.RFC3339)}) + if err != nil { + t.Fatal(err) + } + // Synchronous call, like the endpoint's goroutine would. + s.respondToRequest(nest, req) + + msgs := s.msgs.All(nest.ID) + var reply *Message + for i := range msgs { + if msgs[i].Author == "Hugin" { + reply = &msgs[i] + } + } + events, err := ReadEvents(logPath) + if err != nil { + t.Fatal(err) + } + + if reply == nil { + // No crow message at all: either a flight failure (429/timeout: the + // known free-model instability -> skip) or nothing was even attempted. + flightFailed := false + for _, e := range events { + if e.Kind == "flight" && e.Error != "" { + flightFailed = true + } + } + if flightFailed { + t.Skip("upstream model failed (known free-model instability); the flag path is covered by #11") + } + t.Fatalf("no Hugin reply after a request, log: %+v", msgs) + } + kind := reply.Kind + if kind != "deliver" && kind != "claim" && kind != "decline" { + t.Fatalf("live crow reply must be responsive, got %q (%s)", kind, reply.Text) + } + if reply.To != "owner" { + t.Fatalf("deliver must answer the requester, got to=%q", reply.To) + } + if reply.RefersTo != req.MsgID() { + t.Fatalf("reply must point at the request, got %q", reply.RefersTo) + } + // The call went through Flock.ask, so the flight log has Hugin's flight. + flights := 0 + for _, e := range events { + if e.Crow == "Hugin" && e.Model != "" && !strings.Contains(strings.ToLower(e.Model), "mock") { + flights++ + } + } + if flights == 0 { + t.Fatalf("want a logged non-mock Hugin flight, events: %+v", events) + } +} -- 2.54.0