Files
odidere/internal/llm/llm_integration_test.go
T

189 lines
3.9 KiB
Go
Raw Normal View History

2026-06-26 14:22:05 +00:00
package llm
import (
"context"
"log/slog"
"os"
"testing"
)
// testURL returns the LLM endpoint for integration tests.
// Set ODIDERE_LLM_TEST_URL to enable; tests skip if empty.
func testURL(t *testing.T) string {
t.Helper()
url := os.Getenv("ODIDERE_LLM_TEST_URL")
if url == "" {
t.Skip("ODIDERE_LLM_TEST_URL not set")
}
return url
}
// testKey returns the API key for integration tests.
// Reads ODIDERE_LLM_TEST_KEY; empty string is valid (no auth).
func testKey(t *testing.T) string {
t.Helper()
return os.Getenv("ODIDERE_LLM_TEST_KEY")
}
// testModel returns the model ID for integration tests.
// Reads ODIDERE_LLM_TEST_MODEL; defaults to "test" if empty.
func testModel(t *testing.T) string {
t.Helper()
model := os.Getenv("ODIDERE_LLM_TEST_MODEL")
if model == "" {
return "test"
}
return model
}
func TestIntegration_ListModels(t *testing.T) {
_ = testURL(t)
cfg := Config{
URL: testURL(t),
Key: testKey(t),
Timeout: "30s",
}
2026-09-20 12:49:42 +00:00
client, err := NewClient(cfg, slog.Default())
2026-06-26 14:22:05 +00:00
if err != nil {
t.Fatalf("NewClient: %v", err)
}
models, err := client.ListModels(context.Background())
if err != nil {
t.Fatalf("ListModels: %v", err)
}
if len(models) == 0 {
t.Error("expected at least one model")
}
for _, m := range models {
if m.ID == "" {
t.Error("model has empty ID")
}
}
}
func TestIntegration_Completions(t *testing.T) {
_ = testURL(t)
cfg := Config{
URL: testURL(t),
Key: testKey(t),
Timeout: "60s",
}
2026-09-20 12:49:42 +00:00
client, err := NewClient(cfg, slog.Default())
2026-06-26 14:22:05 +00:00
if err != nil {
t.Fatalf("NewClient: %v", err)
}
2026-06-27 00:13:16 +00:00
res, err := client.Completions(
2026-07-07 15:14:07 +00:00
context.Background(), CompletionsParams{
2026-09-20 12:49:42 +00:00
Model: testModel(t),
2026-07-07 15:14:07 +00:00
Messages: []Message{{
Role: RoleUser,
ContentParts: []ContentPart{{
Type: "text",
Text: "Say hello in three words.",
}},
2026-06-26 14:22:05 +00:00
}},
2026-07-07 15:14:07 +00:00
},
2026-06-26 14:22:05 +00:00
)
if err != nil {
t.Fatalf("Completions: %v", err)
}
2026-09-20 12:49:42 +00:00
text := res.Message.Text()
2026-06-26 14:22:05 +00:00
if text == "" {
t.Error("final message has no text")
}
}
func TestIntegration_CompletionsStream(t *testing.T) {
_ = testURL(t)
cfg := Config{
URL: testURL(t),
Key: testKey(t),
Timeout: "60s",
}
2026-09-20 12:49:42 +00:00
client, err := NewClient(cfg, slog.Default())
2026-06-26 14:22:05 +00:00
if err != nil {
t.Fatalf("NewClient: %v", err)
}
2026-09-20 12:49:42 +00:00
stream, err := client.CompletionsStream(
context.Background(),
CompletionsParams{
Model: testModel(t),
Messages: []Message{{
Role: RoleUser,
ContentParts: []ContentPart{{
Type: "text",
Text: "Say hello in three words.",
2026-06-26 14:22:05 +00:00
}},
2026-09-20 12:49:42 +00:00
}},
},
)
if err != nil {
2026-06-26 14:22:05 +00:00
t.Fatalf("CompletionsStream: %v", err)
}
2026-09-20 12:49:42 +00:00
defer stream.Close()
for stream.Next() {
2026-06-26 14:22:05 +00:00
}
2026-09-20 12:49:42 +00:00
if err := stream.Err(); err != nil {
t.Fatalf("stream.Err: %v", err)
}
result := stream.Result()
if result.Message.Text() == "" {
2026-06-26 14:22:05 +00:00
t.Error("final message has no text")
}
}
func TestIntegration_ToolCalling(t *testing.T) {
_ = testURL(t)
2026-09-20 12:49:42 +00:00
client, err := NewClient(Config{
2026-06-26 14:22:05 +00:00
URL: testURL(t),
Key: testKey(t),
Timeout: "60s",
2026-09-20 12:49:42 +00:00
}, slog.Default())
2026-06-26 14:22:05 +00:00
if err != nil {
t.Fatalf("NewClient: %v", err)
}
2026-06-27 00:13:16 +00:00
res, err := client.Completions(
2026-09-20 12:49:42 +00:00
context.Background(),
CompletionsParams{
Model: testModel(t),
2026-07-07 15:14:07 +00:00
Messages: []Message{{
Role: RoleUser,
ContentParts: []ContentPart{{
Type: "text",
Text: "Use the echo tool to say 'hello'",
}},
2026-06-26 14:22:05 +00:00
}},
2026-09-20 12:49:42 +00:00
Tools: []APITool{{
Type: "function",
Function: &FunctionDef{
Name: "echo",
Description: "Echo back the input string",
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"input": map[string]any{"type": "string"},
},
"required": []string{"input"},
},
},
}},
2026-07-07 15:14:07 +00:00
},
2026-06-26 14:22:05 +00:00
)
if err != nil {
t.Fatalf("Completions: %v", err)
}
2026-09-20 12:49:42 +00:00
if len(res.Message.ToolCalls) == 0 {
t.Fatal("expected a tool call")
2026-06-26 14:22:05 +00:00
}
2026-09-20 12:49:42 +00:00
if res.Message.ToolCalls[0].Function.Name != "echo" {
t.Errorf(
"tool name = %q, want echo",
res.Message.ToolCalls[0].Function.Name,
)
2026-06-26 14:22:05 +00:00
}
}