46c63ed121
Tests: - Add WithTemperature tests (builder method, chaining, zero omission) - Add temperature serialization tests (omitted when 0, included when set) Composite action: - Use python3 for robust JSON version parsing (replaces sed) - Verify SHA-256 checksum before executing downloaded binary - Wire up repo input (no longer hardcodes rodin/review-bot) Release workflow: - Handle 409 conflict (existing release for tag) - Use file-based JSON parsing for reliability Code: - Tighten WithTemperature doc comment (single clear line) - Fix flag alignment (missing tab on llmTemp declaration)
188 lines
5.3 KiB
Go
188 lines
5.3 KiB
Go
package llm
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestComplete_Success(t *testing.T) {
|
|
resp := ChatResponse{
|
|
Choices: []struct {
|
|
Message struct {
|
|
Content string `json:"content"`
|
|
} `json:"message"`
|
|
}{
|
|
{Message: struct {
|
|
Content string `json:"content"`
|
|
}{Content: "Hello, world!"}},
|
|
},
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/chat/completions" {
|
|
t.Errorf("unexpected path: %s", r.URL.Path)
|
|
}
|
|
if r.Method != "POST" {
|
|
t.Errorf("expected POST, got %s", r.Method)
|
|
}
|
|
if r.Header.Get("Authorization") != "Bearer test-key" {
|
|
t.Errorf("unexpected auth: %s", r.Header.Get("Authorization"))
|
|
}
|
|
if r.Header.Get("Content-Type") != "application/json" {
|
|
t.Errorf("unexpected content type: %s", r.Header.Get("Content-Type"))
|
|
}
|
|
|
|
var req ChatRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Fatalf("decode request: %v", err)
|
|
}
|
|
if req.Model != "gpt-4" {
|
|
t.Errorf("expected model %q, got %q", "gpt-4", req.Model)
|
|
}
|
|
if len(req.Messages) != 1 {
|
|
t.Errorf("expected 1 message, got %d", len(req.Messages))
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(resp)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "test-key", "gpt-4")
|
|
got, err := client.Complete([]Message{{Role: "user", Content: "Hi"}})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got != "Hello, world!" {
|
|
t.Errorf("expected %q, got %q", "Hello, world!", got)
|
|
}
|
|
}
|
|
|
|
func TestComplete_APIError(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusTooManyRequests)
|
|
w.Write([]byte(`{"error":"rate limited"}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "test-key", "gpt-4")
|
|
_, err := client.Complete([]Message{{Role: "user", Content: "Hi"}})
|
|
if err == nil {
|
|
t.Fatal("expected error for 429, got nil")
|
|
}
|
|
}
|
|
|
|
func TestComplete_NoChoices(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write([]byte(`{"choices":[]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "test-key", "gpt-4")
|
|
_, err := client.Complete([]Message{{Role: "user", Content: "Hi"}})
|
|
if err == nil {
|
|
t.Fatal("expected error for no choices, got nil")
|
|
}
|
|
}
|
|
|
|
func TestComplete_BadJSON(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(`not json at all`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "test-key", "gpt-4")
|
|
_, err := client.Complete([]Message{{Role: "user", Content: "Hi"}})
|
|
if err == nil {
|
|
t.Fatal("expected error for bad JSON, got nil")
|
|
}
|
|
}
|
|
|
|
func TestComplete_ServerDown(t *testing.T) {
|
|
client := NewClient("http://127.0.0.1:1", "test-key", "gpt-4")
|
|
_, err := client.Complete([]Message{{Role: "user", Content: "Hi"}})
|
|
if err == nil {
|
|
t.Fatal("expected error for connection refused, got nil")
|
|
}
|
|
}
|
|
|
|
func TestWithTemperature(t *testing.T) {
|
|
client := NewClient("http://example.com", "key", "model")
|
|
if client.Temperature != 0 {
|
|
t.Errorf("expected initial temperature 0, got %f", client.Temperature)
|
|
}
|
|
|
|
result := client.WithTemperature(0.7)
|
|
if result != client {
|
|
t.Error("WithTemperature should return the same client for chaining")
|
|
}
|
|
if client.Temperature != 0.7 {
|
|
t.Errorf("expected temperature 0.7, got %f", client.Temperature)
|
|
}
|
|
}
|
|
|
|
func TestComplete_TemperatureOmittedWhenZero(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var req map[string]interface{}
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
if _, exists := req["temperature"]; exists {
|
|
t.Error("temperature should be omitted when zero (server default)")
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(ChatResponse{
|
|
Choices: []struct {
|
|
Message struct {
|
|
Content string `json:"content"`
|
|
} `json:"message"`
|
|
}{{Message: struct {
|
|
Content string `json:"content"`
|
|
}{Content: "ok"}}},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "key", "model")
|
|
_, err := client.Complete([]Message{{Role: "user", Content: "Hi"}})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestComplete_TemperatureIncludedWhenSet(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var req map[string]interface{}
|
|
json.NewDecoder(r.Body).Decode(&req)
|
|
|
|
temp, exists := req["temperature"]
|
|
if !exists {
|
|
t.Error("temperature should be included when set")
|
|
}
|
|
if temp != 0.7 {
|
|
t.Errorf("expected temperature 0.7, got %v", temp)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(ChatResponse{
|
|
Choices: []struct {
|
|
Message struct {
|
|
Content string `json:"content"`
|
|
} `json:"message"`
|
|
}{{Message: struct {
|
|
Content string `json:"content"`
|
|
}{Content: "ok"}}},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient(server.URL, "key", "model").WithTemperature(0.7)
|
|
_, err := client.Complete([]Message{{Role: "user", Content: "Hi"}})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|