package main import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" ) func fakeChat(t *testing.T, status int, reply string) (*ChatBrain, *http.Request) { t.Helper() var got http.Request srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { got = *r var body map[string]any if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Errorf("request body is not JSON: %v", err) } if body["model"] != "openrouter/free" { t.Errorf("model = %v", body["model"]) } w.WriteHeader(status) w.Write([]byte(reply)) })) t.Cleanup(srv.Close) return &ChatBrain{provider: "OpenRouter", url: srv.URL, key: "k", model: "openrouter/free", client: srv.Client(), headers: map[string]string{"X-OpenRouter-Title": "So Many Crows"}}, &got } func TestChatBrainReply(t *testing.T) { b, req := fakeChat(t, 200, `{"model":"some/model:free","choices":[{"message":{"content":"caw"}}],"usage":{"prompt_tokens":10,"completion_tokens":5}}`) var model string text, tokens, err := b.Ask(withModelSlot(context.Background(), &model), "sys", "hi") if err != nil || text != "caw" || tokens != 15 || model != "some/model:free" { t.Fatalf("got %q %d %q %v", text, tokens, model, err) } if req.Header.Get("Authorization") != "Bearer k" || req.Header.Get("X-OpenRouter-Title") != "So Many Crows" { t.Fatalf("headers: %v", req.Header) } } func TestChatBrainEstimatesTokensWhenUsageMissing(t *testing.T) { b, _ := fakeChat(t, 200, `{"choices":[{"message":{"content":"`+strings.Repeat("a", 400)+`"}}]}`) _, tokens, err := b.Ask(context.Background(), "", "") if err != nil || tokens != 100 { t.Fatalf("tokens = %d, err %v; want the 4-chars-per-token estimate so budgets still bite", tokens, err) } } func TestChatBrainErrors(t *testing.T) { for name, c := range map[string]struct { status int body string want string }{ "rate limit": {429, `{"error":{"message":"Rate limit exceeded"}}`, "429"}, "error body": {200, `{"error":{"message":"no free models"}}`, "no free models"}, "empty": {200, `{"choices":[{"message":{"content":" "}}]}`, "empty reply"}, } { b, _ := fakeChat(t, c.status, c.body) if _, _, err := b.Ask(context.Background(), "s", "p"); err == nil || !strings.Contains(err.Error(), c.want) { t.Errorf("%s: err = %v, want %q", name, err, c.want) } } } func TestNewBrainPicksProvider(t *testing.T) { t.Setenv("ANTHROPIC_API_KEY", "") t.Setenv("OPENROUTER_API_KEY", "or") t.Setenv("CROWS_PROVIDER", "") t.Setenv("CROWS_MODEL", "") if got := NewBrain().Name(); got != "OpenRouter (openrouter/free)" { t.Fatalf("got %q", got) } t.Setenv("CROWS_PROVIDER", "anthropic") if got := NewBrain().Name(); got != "offline mock" { t.Fatalf("anthropic with no key should fall back to mock, got %q", got) } }