返回 DeepSeek-Reasonix
fetch_models_test.go
根目录 / internal / provider / openai / fetch_models_test.go
1 package openai
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11 )
12
13 func TestFetchModels(t *testing.T) {
14 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
15 if r.URL.Path != "/models" {
16 http.NotFound(w, r)
17 return
18 }
19 if r.Header.Get("Authorization") != "Bearer test-key" {
20 http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
21 return
22 }
23 json.NewEncoder(w).Encode(map[string]any{
24 "object": "list",
25 "data": []map[string]string{
26 {"id": "model-b", "object": "model"},
27 {"id": "model-a", "object": "model"},
28 },
29 })
30 }))
31 defer srv.Close()
32
33 models, err := FetchModels(context.Background(), srv.URL, "test-key", nil)
34 if err != nil {
35 t.Fatalf("unexpected error: %v", err)
36 }
37 if len(models) != 2 {
38 t.Fatalf("want 2 models, got %d", len(models))
39 }
40 if models[0] != "model-a" || models[1] != "model-b" {
41 t.Errorf("want sorted [model-a model-b], got %v", models)
42 }
43 }
44
45 func TestFetchModelsSendsCustomHeaders(t *testing.T) {
46 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
47 if r.Header.Get("HTTP-Referer") != "https://app.example" || r.Header.Get("X-Title") != "Reasonix" {
48 http.Error(w, `{"error":"missing headers"}`, http.StatusForbidden)
49 return
50 }
51 json.NewEncoder(w).Encode(map[string]any{
52 "data": []map[string]string{{"id": "model-a"}},
53 })
54 }))
55 defer srv.Close()
56
57 models, err := FetchModels(context.Background(), srv.URL, "key", map[string]string{
58 "HTTP-Referer": "https://app.example",
59 "X-Title": "Reasonix",
60 })
61 if err != nil {
62 t.Fatalf("FetchModels: %v", err)
63 }
64 if len(models) != 1 || models[0] != "model-a" {
65 t.Fatalf("models = %v, want [model-a]", models)
66 }
67 }
68
69 func TestFetchModelsWithOptionsUsesXAPIKey(t *testing.T) {
70 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
71 if got := r.Header.Get("x-api-key"); got != "anthropic-key" {
72 http.Error(w, `{"error":"missing x-api-key"}`, http.StatusUnauthorized)
73 return
74 }
75 if got := r.Header.Get("Authorization"); got != "" {
76 http.Error(w, `{"error":"unexpected bearer"}`, http.StatusUnauthorized)
77 return
78 }
79 json.NewEncoder(w).Encode(map[string]any{
80 "data": []map[string]string{{"id": "anthropic-model"}},
81 })
82 }))
83 defer srv.Close()
84
85 models, err := FetchModelsWithOptions(context.Background(), srv.URL, "anthropic-key", FetchModelsOptions{
86 AuthMode: ModelFetchAuthXAPIKey,
87 })
88 if err != nil {
89 t.Fatalf("FetchModelsWithOptions: %v", err)
90 }
91 if len(models) != 1 || models[0] != "anthropic-model" {
92 t.Fatalf("models = %v, want [anthropic-model]", models)
93 }
94 }
95
96 func TestApplyAPIKeyHeaderUsesMiMoAPIKeyHeader(t *testing.T) {
97 h := http.Header{}
98 applyAPIKeyHeader(h, "https://api.xiaomimimo.com/v1", "mimo-key")
99 if got := h.Get("api-key"); got != "mimo-key" {
100 t.Fatalf("api-key = %q, want mimo-key", got)
101 }
102 if got := h.Get("Authorization"); got != "" {
103 t.Fatalf("Authorization = %q, want omitted for MiMo", got)
104 }
105
106 h = http.Header{}
107 applyAPIKeyHeader(h, "https://api.deepseek.com", "deepseek-key")
108 if got := h.Get("Authorization"); got != "Bearer deepseek-key" {
109 t.Fatalf("Authorization = %q, want Bearer deepseek-key", got)
110 }
111 if got := h.Get("api-key"); got != "" {
112 t.Fatalf("api-key = %q, want omitted for standard OpenAI-compatible providers", got)
113 }
114 }
115
116 func TestFetchModelsAuthError(t *testing.T) {
117 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118 http.Error(w, `{"error":{"message":"invalid key"}}`, http.StatusUnauthorized)
119 }))
120 defer srv.Close()
121
122 _, err := FetchModels(context.Background(), srv.URL, "bad-key", nil)
123 if err == nil {
124 t.Fatal("expected error for bad key")
125 }
126 }
127
128 func TestFetchModelsLargeResponse(t *testing.T) {
129 // A model list larger than the old 256 KB cap should succeed.
130 // OpenRouter returns ~531 KB (338 models); this test generates
131 // enough entries to exceed 256 KB and confirms they are all parsed.
132 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
133 data := make([]map[string]string, 8000)
134 for i := range data {
135 data[i] = map[string]string{"id": fmt.Sprintf("model-%04d", i), "object": "model"}
136 }
137 json.NewEncoder(w).Encode(map[string]any{"object": "list", "data": data})
138 }))
139 defer srv.Close()
140
141 models, err := FetchModels(context.Background(), srv.URL, "key", nil)
142 if err != nil {
143 t.Fatalf("unexpected error: %v", err)
144 }
145 if len(models) != 8000 {
146 t.Fatalf("want 8000 models, got %d", len(models))
147 }
148 }
149
150 func TestFetchModelsResponseTooLarge(t *testing.T) {
151 // A response larger than fetchModelsMaxBody should return a clear
152 // error rather than a cryptic JSON parse failure.
153 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
154 w.Header().Set("Content-Type", "application/json")
155 w.WriteHeader(http.StatusOK)
156 padding := strings.Repeat("x", fetchModelsMaxBody+1024)
157 fmt.Fprintf(w, `{"object":"list","data":[{"id":"%s","object":"model"}]}`, padding)
158 }))
159 defer srv.Close()
160
161 _, err := FetchModels(context.Background(), srv.URL, "key", nil)
162 if err == nil {
163 t.Fatal("expected error for oversized response")
164 }
165 if !strings.Contains(err.Error(), "too large") {
166 t.Errorf("error should mention the size limit, got: %v", err)
167 }
168 }
169
169 lines GO