summaryrefslogtreecommitdiff
path: root/internal/llm/anthropic_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/llm/anthropic_test.go')
-rw-r--r--internal/llm/anthropic_test.go259
1 files changed, 259 insertions, 0 deletions
diff --git a/internal/llm/anthropic_test.go b/internal/llm/anthropic_test.go
new file mode 100644
index 0000000..15756f5
--- /dev/null
+++ b/internal/llm/anthropic_test.go
@@ -0,0 +1,259 @@
+package llm
+
+import (
+ "context"
+ "encoding/json"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+)
+
+func TestAnthropicChat_Success(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ t.Fatalf("expected POST, got %s", r.Method)
+ }
+ if !strings.HasSuffix(r.URL.Path, "/messages") {
+ t.Fatalf("expected /messages endpoint, got %s", r.URL.Path)
+ }
+ // Check headers
+ if r.Header.Get("x-api-key") != "test-key" {
+ t.Fatalf("expected x-api-key header")
+ }
+ if r.Header.Get("anthropic-version") != "2023-06-01" {
+ t.Fatalf("expected anthropic-version header")
+ }
+ // Verify request body
+ var req anthropicChatRequest
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ t.Fatalf("failed to decode request: %v", err)
+ }
+ if req.Model != "claude-3-5-sonnet-20241022" {
+ t.Fatalf("expected model claude-3-5-sonnet-20241022, 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.Fatalf("expected user role, got %s", req.Messages[0].Role)
+ }
+ if req.Messages[0].Content != "Hello" {
+ t.Fatalf("expected content 'Hello', got '%s'", req.Messages[0].Content)
+ }
+ // Send response
+ resp := anthropicChatResponse{
+ ID: "msg-123",
+ Type: "message",
+ StopReason: "end_turn",
+ Content: []struct {
+ Type string `json:"type"`
+ Text string `json:"text"`
+ }{
+ {Type: "text", Text: "Hi there!"},
+ },
+ }
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(resp)
+ }))
+ defer srv.Close()
+
+ c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient)
+ response, err := c.Chat(context.Background(), []Message{
+ {Role: "user", Content: "Hello"},
+ })
+ if err != nil {
+ t.Fatalf("Chat failed: %v", err)
+ }
+ if response != "Hi there!" {
+ t.Fatalf("expected 'Hi there!', got '%s'", response)
+ }
+}
+
+func TestAnthropicChat_NoAPIKey(t *testing.T) {
+ c := newAnthropic("https://api.anthropic.com/v1", "claude-3-5-sonnet-20241022", "", nil)
+ _, err := c.Chat(context.Background(), []Message{
+ {Role: "user", Content: "Hello"},
+ })
+ if err == nil {
+ t.Fatalf("expected error for missing API key")
+ }
+ if !strings.Contains(err.Error(), "missing Anthropic API key") {
+ t.Fatalf("expected 'missing Anthropic API key', got '%s'", err.Error())
+ }
+}
+
+func TestAnthropicChat_APIError(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusUnauthorized)
+ resp := anthropicChatResponse{
+ Error: &struct {
+ Type string `json:"type"`
+ Message string `json:"message"`
+ }{
+ Type: "authentication_error",
+ Message: "Invalid API key",
+ },
+ }
+ json.NewEncoder(w).Encode(resp)
+ }))
+ defer srv.Close()
+
+ c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "invalid-key", nil).(anthropicClient)
+ _, err := c.Chat(context.Background(), []Message{
+ {Role: "user", Content: "Hello"},
+ })
+ if err == nil {
+ t.Fatalf("expected error for API error response")
+ }
+ if !strings.Contains(err.Error(), "Invalid API key") {
+ t.Fatalf("expected 'Invalid API key' in error, got '%s'", err.Error())
+ }
+}
+
+func TestAnthropicChat_EmptyResponse(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ resp := anthropicChatResponse{
+ ID: "msg-123",
+ Type: "message",
+ StopReason: "end_turn",
+ Content: []struct {
+ Type string `json:"type"`
+ Text string `json:"text"`
+ }{},
+ }
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(resp)
+ }))
+ defer srv.Close()
+
+ c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient)
+ _, err := c.Chat(context.Background(), []Message{
+ {Role: "user", Content: "Hello"},
+ })
+ if err == nil {
+ t.Fatalf("expected error for empty content")
+ }
+ if !strings.Contains(err.Error(), "no content returned") {
+ t.Fatalf("expected 'no content returned', got '%s'", err.Error())
+ }
+}
+
+func TestAnthropicChat_WithTemperature(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ var req anthropicChatRequest
+ json.NewDecoder(r.Body).Decode(&req)
+ if req.Temperature == nil || *req.Temperature != 0.5 {
+ t.Fatalf("expected temperature 0.5, got %v", req.Temperature)
+ }
+ resp := anthropicChatResponse{
+ ID: "msg-123",
+ Type: "message",
+ StopReason: "end_turn",
+ Content: []struct {
+ Type string `json:"type"`
+ Text string `json:"text"`
+ }{
+ {Type: "text", Text: "Response"},
+ },
+ }
+ w.Header().Set("Content-Type", "application/json")
+ json.NewEncoder(w).Encode(resp)
+ }))
+ defer srv.Close()
+
+ c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient)
+ _, err := c.Chat(context.Background(), []Message{
+ {Role: "user", Content: "Hello"},
+ }, WithTemperature(0.5))
+ if err != nil {
+ t.Fatalf("Chat failed: %v", err)
+ }
+}
+
+func TestAnthropicStream_Success(t *testing.T) {
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/event-stream")
+ // Send streaming response
+ streamEvents := []string{
+ `data: {"type":"message_start","message":{"id":"msg-123","type":"message"}}`,
+ `data: {"type":"content_block_start","content_block":{"type":"text"}}`,
+ `data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Hello"}}`,
+ `data: {"type":"content_block_delta","delta":{"type":"text_delta","text":" "}}`,
+ `data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"world"}}`,
+ `data: {"type":"message_stop"}`,
+ }
+ for _, event := range streamEvents {
+ io.WriteString(w, event+"\n")
+ }
+ }))
+ defer srv.Close()
+
+ c := newAnthropic(srv.URL, "claude-3-5-sonnet-20241022", "test-key", nil)
+ streamer, ok := c.(Streamer)
+ if !ok {
+ t.Fatalf("Anthropic client does not implement Streamer interface")
+ }
+ var chunks []string
+ err := streamer.ChatStream(context.Background(), []Message{
+ {Role: "user", Content: "Say hello"},
+ }, func(chunk string) {
+ chunks = append(chunks, chunk)
+ })
+ if err != nil {
+ t.Fatalf("ChatStream failed: %v", err)
+ }
+ if len(chunks) != 3 {
+ t.Fatalf("expected 3 chunks, got %d", len(chunks))
+ }
+ if chunks[0] != "Hello" || chunks[1] != " " || chunks[2] != "world" {
+ t.Fatalf("unexpected chunks: %v", chunks)
+ }
+}
+
+func TestAnthropicStream_NoAPIKey(t *testing.T) {
+ c := newAnthropic("https://api.anthropic.com/v1", "claude-3-5-sonnet-20241022", "", nil)
+ streamer, ok := c.(Streamer)
+ if !ok {
+ t.Fatalf("Anthropic client does not implement Streamer interface")
+ }
+ err := streamer.ChatStream(context.Background(), []Message{
+ {Role: "user", Content: "Hello"},
+ }, func(chunk string) {})
+ if err == nil {
+ t.Fatalf("expected error for missing API key")
+ }
+ if !strings.Contains(err.Error(), "missing Anthropic API key") {
+ t.Fatalf("expected 'missing Anthropic API key', got '%s'", err.Error())
+ }
+}
+
+func TestAnthropicClient_Name(t *testing.T) {
+ c := newAnthropic("https://api.anthropic.com/v1", "claude-3-5-sonnet-20241022", "test-key", nil)
+ if c.Name() != "anthropic" {
+ t.Fatalf("expected 'anthropic', got '%s'", c.Name())
+ }
+}
+
+func TestAnthropicClient_DefaultModel(t *testing.T) {
+ model := "claude-3-opus-20250219"
+ c := newAnthropic("https://api.anthropic.com/v1", model, "test-key", nil).(anthropicClient)
+ if c.DefaultModel() != model {
+ t.Fatalf("expected '%s', got '%s'", model, c.DefaultModel())
+ }
+}
+
+func TestAnthropicClient_DefaultBaseURL(t *testing.T) {
+ c := newAnthropic("", "claude-3-5-sonnet-20241022", "test-key", nil).(anthropicClient)
+ if c.baseURL != "https://api.anthropic.com/v1" {
+ t.Fatalf("expected default base URL, got '%s'", c.baseURL)
+ }
+}
+
+func TestAnthropicClient_DefaultModel_Empty(t *testing.T) {
+ c := newAnthropic("https://api.anthropic.com/v1", "", "test-key", nil).(anthropicClient)
+ if c.defaultModel != "claude-3-5-sonnet-20241022" {
+ t.Fatalf("expected default model, got '%s'", c.defaultModel)
+ }
+}