package lem import ( "encoding/json" "net/http" "net/http/httptest" "sync/atomic" "testing" ) func TestClientChat(t *testing.T) { // Mock server returns a valid ChatResponse. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Verify request method and path. if r.Method != http.MethodPost { t.Errorf("expected POST, got %s", r.Method) } if r.URL.Path != "/v1/chat/completions" { t.Errorf("expected /v1/chat/completions, got %s", r.URL.Path) } if ct := r.Header.Get("Content-Type"); ct != "application/json" { t.Errorf("expected application/json content-type, got %s", ct) } // Verify request body structure. var req ChatRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { t.Fatalf("failed to decode request body: %v", err) } if req.Model != "test-model" { t.Errorf("expected model test-model, got %s", req.Model) } if len(req.Messages) != 1 { t.Fatalf("expected 1 message, got %d", len(req.Messages)) } if req.Messages[0].Role != "user" { t.Errorf("expected role user, got %s", req.Messages[0].Role) } if req.Messages[0].Content != "Hello" { t.Errorf("expected content Hello, got %s", req.Messages[0].Content) } if req.Temperature != 0.1 { t.Errorf("expected temperature 0.1, got %f", req.Temperature) } // Return a valid response. resp := ChatResponse{ Choices: []Choice{ { Message: Message{ Role: "assistant", Content: "Hi there!", }, }, }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) })) defer server.Close() client := NewClient(server.URL, "test-model") result, err := client.Chat("Hello") if err != nil { t.Fatalf("unexpected error: %v", err) } if result != "Hi there!" { t.Errorf("expected 'Hi there!', got %q", result) } } func TestClientChatWithTemp(t *testing.T) { // Verify that ChatWithTemp sends the correct temperature. server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req ChatRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { t.Fatalf("failed to decode request body: %v", err) } if req.Temperature != 0.7 { t.Errorf("expected temperature 0.7, got %f", req.Temperature) } resp := ChatResponse{ Choices: []Choice{ {Message: Message{Role: "assistant", Content: "creative response"}}, }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) })) defer server.Close() client := NewClient(server.URL, "test-model") result, err := client.ChatWithTemp("Be creative", 0.7) if err != nil { t.Fatalf("unexpected error: %v", err) } if result != "creative response" { t.Errorf("expected 'creative response', got %q", result) } } func TestClientRetry(t *testing.T) { // Mock server fails twice with 500, then succeeds on third attempt. var attempts atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { n := attempts.Add(1) if n <= 2 { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("server error")) return } resp := ChatResponse{ Choices: []Choice{ {Message: Message{Role: "assistant", Content: "finally worked"}}, }, } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) })) defer server.Close() client := NewClient(server.URL, "test-model") result, err := client.Chat("retry me") if err != nil { t.Fatalf("unexpected error after retries: %v", err) } if result != "finally worked" { t.Errorf("expected 'finally worked', got %q", result) } if got := attempts.Load(); got != 3 { t.Errorf("expected 3 attempts, got %d", got) } } func TestClientRetryExhausted(t *testing.T) { // Mock server always fails - should exhaust all 3 retries. var attempts atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts.Add(1) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("permanent failure")) })) defer server.Close() client := NewClient(server.URL, "test-model") _, err := client.Chat("will fail") if err == nil { t.Fatal("expected error after exhausting retries, got nil") } if got := attempts.Load(); got != 3 { t.Errorf("expected 3 attempts, got %d", got) } } func TestClientEmptyChoices(t *testing.T) { // Mock server returns response with no choices -- should fail without retrying. var attempts atomic.Int32 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { attempts.Add(1) resp := ChatResponse{Choices: []Choice{}} w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(resp) })) defer server.Close() client := NewClient(server.URL, "test-model") _, err := client.Chat("empty response") if err == nil { t.Fatal("expected error for empty choices, got nil") } if got := attempts.Load(); got != 1 { t.Errorf("expected 1 attempt (no retries for non-transient errors), got %d", got) } }