1package provider
 2
 3import (
 4	"context"
 5	"encoding/json"
 6	"net/http"
 7	"net/http/httptest"
 8	"os"
 9	"testing"
10	"time"
11
12	"github.com/charmbracelet/crush/internal/config"
13	"github.com/charmbracelet/crush/internal/fur/provider"
14	"github.com/charmbracelet/crush/internal/llm/tools"
15	"github.com/charmbracelet/crush/internal/message"
16	"github.com/openai/openai-go"
17	"github.com/openai/openai-go/option"
18)
19
20func TestMain(m *testing.M) {
21	_, err := config.Init(".", true)
22	if err != nil {
23		panic("Failed to initialize config: " + err.Error())
24	}
25
26	os.Exit(m.Run())
27}
28
29func TestOpenAIClientStreamChoices(t *testing.T) {
30	// Create a mock server that returns Server-Sent Events with empty choices
31	// This simulates the 🤡 behavior when a server returns 200 instead of 404
32	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
33		w.Header().Set("Content-Type", "text/event-stream")
34		w.Header().Set("Cache-Control", "no-cache")
35		w.Header().Set("Connection", "keep-alive")
36		w.WriteHeader(http.StatusOK)
37
38		emptyChoicesChunk := map[string]any{
39			"id":      "chat-completion-test",
40			"object":  "chat.completion.chunk",
41			"created": time.Now().Unix(),
42			"model":   "test-model",
43			"choices": []any{}, // Empty choices array that causes panic
44		}
45
46		jsonData, _ := json.Marshal(emptyChoicesChunk)
47		w.Write([]byte("data: " + string(jsonData) + "\n\n"))
48		w.Write([]byte("data: [DONE]\n\n"))
49	}))
50	defer server.Close()
51
52	// Create OpenAI client pointing to our mock server
53	client := &openaiClient{
54		providerOptions: providerClientOptions{
55			modelType:     config.SelectedModelTypeLarge,
56			apiKey:        "test-key",
57			systemMessage: "test",
58			model: func(config.SelectedModelType) provider.Model {
59				return provider.Model{
60					ID:    "test-model",
61					Model: "test-model",
62				}
63			},
64		},
65		client: openai.NewClient(
66			option.WithAPIKey("test-key"),
67			option.WithBaseURL(server.URL),
68		),
69	}
70
71	// Create test messages
72	messages := []message.Message{
73		{
74			Role:  message.User,
75			Parts: []message.ContentPart{message.TextContent{Text: "Hello"}},
76		},
77	}
78
79	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
80	defer cancel()
81
82	eventsChan := client.stream(ctx, messages, []tools.BaseTool{})
83
84	// Collect events - this will panic without the bounds check
85	for event := range eventsChan {
86		t.Logf("Received event: %+v", event)
87		if event.Type == EventError || event.Type == EventComplete {
88			break
89		}
90	}
91}