package llm
import (
"context"
"fmt"
"net/http"
"strings"
"testing"
)
func TestListModelsReadsEveryPage(t *testing.T) {
var paths []string
var headers http.Header
c := testClient(t, func(w http.ResponseWriter, r *http.Request) {
headers = r.Header.Clone()
paths = append(paths, r.URL.RequestURI())
if after := r.URL.Query().Get("after_id"); after == "" {
fmt.Fprint(w, `{"data":[
{"type":"model","id":"claude-opus-5","display_name":"Claude Opus 5","created_at":"2026-02-01T00:00:00Z"},
{"type":"model","id":"claude-sonnet-5","display_name":"Claude Sonnet 5","created_at":"2025-09-29T00:00:00Z"}],
"has_more":true,"last_id":"claude-sonnet-5"}`)
return
}
fmt.Fprint(w, `{"data":[
{"type":"model","id":"claude-haiku-4-5-20251001","display_name":"Claude Haiku 4.5","created_at":"2025-10-01T00:00:00Z"}],
"has_more":false}`)
})
got, err := c.ListModels(context.Background())
if err != nil {
t.Fatalf("ListModels: %v", err)
}
var ids []string
for _, m := range got {
ids = append(ids, m.ID)
}
if want := "claude-opus-5 claude-sonnet-5 claude-haiku-4-5-20251001"; strings.Join(ids, " ") != want {
t.Errorf("ids = %v, want %s in the order the API gave them", ids, want)
}
if got[0].DisplayName != "Claude Opus 5" {
t.Errorf("display name = %q", got[0].DisplayName)
}
if len(paths) != 2 {
t.Fatalf("requests = %v, want a second one for the second page", paths)
}
if !strings.HasPrefix(paths[0], "/v1/models?") {
t.Errorf("path = %q, want /v1/models", paths[0])
}
if !strings.Contains(paths[1], "after_id=claude-sonnet-5") {
t.Errorf("second request = %q, want it to carry the last id", paths[1])
}
if headers.Get("x-api-key") != "test-key" || headers.Get("anthropic-version") == "" {
t.Errorf("credentials or version missing: %v", headers)
}
}
// Not every endpoint serves this route. The error has to name what went wrong
// so the picker can say it out loud.
func TestListModelsReportsAnEndpointThatWillNotAnswer(t *testing.T) {
c := testClient(t, func(w http.ResponseWriter, r *http.Request) {
http.Error(w, `{"type":"error","error":{"type":"not_found_error","message":"unknown route"}}`, http.StatusNotFound)
})
_, err := c.ListModels(context.Background())
if err == nil {
t.Fatal("a 404 produced no error")
}
if !strings.Contains(err.Error(), "404") && !strings.Contains(err.Error(), "not_found") {
t.Errorf("error = %q, want it to say what the endpoint answered", err)
}
}
// A bearer credential is what OAuth tokens and most gateways use, and the
// listing has to authenticate the same way the messages call does.
func TestListModelsSendsABearerCredential(t *testing.T) {
var headers http.Header
c := testClient(t, func(w http.ResponseWriter, r *http.Request) {
headers = r.Header.Clone()
fmt.Fprint(w, `{"data":[],"has_more":false}`)
})
WithAuthToken("oauth-token")(c)
if _, err := c.ListModels(context.Background()); err != nil {
t.Fatalf("ListModels: %v", err)
}
if got := headers.Get("authorization"); got != "Bearer oauth-token" {
t.Errorf("authorization = %q", got)
}
if headers.Get("anthropic-beta") == "" {
t.Error("the oauth beta flag is missing")
}
}
func TestListModelsRefusesWithoutCredentials(t *testing.T) {
c := New(WithBaseURL("http://127.0.0.1:1"))
c.apiKey, c.authToken = "", ""
if _, err := c.ListModels(context.Background()); err == nil {
t.Fatal("listing without credentials should not even be attempted")
}
}