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", } client, err := NewClient(cfg, slog.Default()) 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", } client, err := NewClient(cfg, slog.Default()) if err != nil { t.Fatalf("NewClient: %v", err) } res, err := client.Completions( context.Background(), CompletionsParams{ Model: testModel(t), Messages: []Message{{ Role: RoleUser, ContentParts: []ContentPart{{ Type: "text", Text: "Say hello in three words.", }}, }}, }, ) if err != nil { t.Fatalf("Completions: %v", err) } text := res.Message.Text() 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", } client, err := NewClient(cfg, slog.Default()) if err != nil { t.Fatalf("NewClient: %v", err) } stream, err := client.CompletionsStream( context.Background(), CompletionsParams{ Model: testModel(t), Messages: []Message{{ Role: RoleUser, ContentParts: []ContentPart{{ Type: "text", Text: "Say hello in three words.", }}, }}, }, ) if err != nil { t.Fatalf("CompletionsStream: %v", err) } defer stream.Close() for stream.Next() { } if err := stream.Err(); err != nil { t.Fatalf("stream.Err: %v", err) } result := stream.Result() if result.Message.Text() == "" { t.Error("final message has no text") } } func TestIntegration_ToolCalling(t *testing.T) { _ = testURL(t) client, err := NewClient(Config{ URL: testURL(t), Key: testKey(t), Timeout: "60s", }, slog.Default()) if err != nil { t.Fatalf("NewClient: %v", err) } res, err := client.Completions( context.Background(), CompletionsParams{ Model: testModel(t), Messages: []Message{{ Role: RoleUser, ContentParts: []ContentPart{{ Type: "text", Text: "Use the echo tool to say 'hello'", }}, }}, 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"}, }, }, }}, }, ) if err != nil { t.Fatalf("Completions: %v", err) } if len(res.Message.ToolCalls) == 0 { t.Fatal("expected a tool call") } if res.Message.ToolCalls[0].Function.Name != "echo" { t.Errorf( "tool name = %q, want echo", res.Message.ToolCalls[0].Function.Name, ) } }