package chatcmpl import ( "context" "errors" "io" "net/http" "net/http/httptest" "strings" "testing" ) func TestCreateNonStream(t *testing.T) { var gotAuth, gotBody string upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotAuth = r.Header.Get("Authorization") data, _ := io.ReadAll(r.Body) gotBody = string(data) w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"id":"cmpl-1","choices":[{"index":0,"message":{"role":"assistant","content":"hi"},"finish_reason":"stop"}],"usage":{"prompt_tokens":5,"completion_tokens":2,"total_tokens":7}}`)) })) defer upstream.Close() client := NewClient(upstream.URL, "sk-up") resp, err := client.Create(context.Background(), &Request{Model: "ds-chat", Messages: []Message{{Role: "user", Content: "hi"}}}) if err != nil { t.Fatalf("Create: %v", err) } if gotAuth != "Bearer sk-up" { t.Errorf("authorization = %q", gotAuth) } if !strings.Contains(gotBody, `"model":"ds-chat"`) { t.Errorf("request body = %s", gotBody) } if len(resp.Choices) != 1 || resp.Choices[0].Message.Content != "hi" { t.Fatalf("response = %+v", resp) } if resp.Usage == nil || resp.Usage.PromptTokens != 5 { t.Errorf("usage = %+v", resp.Usage) } } func TestCreateUpstreamError(t *testing.T) { upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) _, _ = w.Write([]byte(`{"error":{"message":"bad key","type":"invalid_api_key"}}`)) })) defer upstream.Close() client := NewClient(upstream.URL, "sk-bad") _, err := client.Create(context.Background(), &Request{Model: "m"}) var ue *UpstreamError if !errors.As(err, &ue) { t.Fatalf("error = %v, want *UpstreamError", err) } if ue.StatusCode != http.StatusUnauthorized || ue.Message != "bad key" { t.Errorf("upstream error = %+v", ue) } } func TestCreateStreamRejectsNon2xx(t *testing.T) { upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTooManyRequests) _, _ = w.Write([]byte(`{"error":{"message":"slow down"}}`)) })) defer upstream.Close() client := NewClient(upstream.URL, "sk") resp, err := client.CreateStream(context.Background(), &Request{Model: "m", Stream: true}) if err == nil { resp.Body.Close() t.Fatal("CreateStream succeeded on 429") } } func TestListModels(t *testing.T) { var gotPath, gotAuth string upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPath = r.URL.Path gotAuth = r.Header.Get("Authorization") w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"object":"list","data":[{"id":"deepseek-chat","object":"model","owned_by":"deepseek"},{"id":"deepseek-reasoner"}]}`)) })) defer upstream.Close() client := NewClient(upstream.URL, "sk-up") list, err := client.ListModels(context.Background()) if err != nil { t.Fatalf("ListModels: %v", err) } if gotPath != "/models" { t.Errorf("path = %q, want /models", gotPath) } if gotAuth != "Bearer sk-up" { t.Errorf("authorization = %q", gotAuth) } if len(list.Data) != 2 || list.Data[0].ID != "deepseek-chat" { t.Fatalf("models = %+v", list.Data) } } func TestListModelsUpstreamError(t *testing.T) { upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusUnauthorized) _, _ = w.Write([]byte(`{"error":{"message":"bad key"}}`)) })) defer upstream.Close() _, err := NewClient(upstream.URL, "sk-bad").ListModels(context.Background()) var ue *UpstreamError if !errors.As(err, &ue) { t.Fatalf("error = %v, want *UpstreamError", err) } if ue.StatusCode != http.StatusUnauthorized || ue.Message != "bad key" { t.Errorf("upstream error = %+v", ue) } } func TestStreamNext(t *testing.T) { sse := strings.Join([]string{ `data: {"id":"1","choices":[{"index":0,"delta":{"content":"Hel"}}]}`, "", `: keep-alive comment`, `data: {"id":"1","choices":[{"index":0,"delta":{"content":"lo"}}]}`, "", `data: {"id":"1","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":3,"completion_tokens":1}}`, "", "data: [DONE]", "", }, "\n") stream := NewStream(strings.NewReader(sse)) first, err := stream.Next() if err != nil { t.Fatalf("Next: %v", err) } if first.Choices[0].Delta.Content == nil || *first.Choices[0].Delta.Content != "Hel" { t.Fatalf("first chunk = %+v", first) } second, err := stream.Next() if err != nil { t.Fatalf("Next: %v", err) } if *second.Choices[0].Delta.Content != "lo" { t.Fatalf("second chunk = %+v", second) } third, err := stream.Next() if err != nil { t.Fatalf("Next: %v", err) } if third.Choices[0].FinishReason == nil || *third.Choices[0].FinishReason != "stop" { t.Fatalf("third chunk = %+v", third) } if third.Usage == nil || third.Usage.CompletionTokens != 1 { t.Fatalf("usage chunk = %+v", third) } if _, err := stream.Next(); err != io.EOF { t.Fatalf("final Next err = %v, want io.EOF", err) } }