- Add comprehensive MockLLMClient tests (generate, stream, match variants) - Add TypeRef JSON marshaling tests and StopReason value tests - Add Ollama embedder tests for config defaults and embedding requests
237 lines
6.7 KiB
Go
237 lines
6.7 KiB
Go
package mock
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/VictorVargas/rony-llm-agent/pkg/llm"
|
|
)
|
|
|
|
func TestMockLLMClient_Generate_Default(t *testing.T) {
|
|
client := New()
|
|
resp, err := client.Generate(context.Background(), llm.CompletionRequest{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.Content != "default response" {
|
|
t.Errorf("expected 'default response', got %q", resp.Content)
|
|
}
|
|
if resp.StopReason != llm.StopReasonEndTurn {
|
|
t.Errorf("expected StopReasonEndTurn, got %q", resp.StopReason)
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_Generate_Custom(t *testing.T) {
|
|
expected := llm.CompletionResponse{
|
|
ID: "test_123",
|
|
Model: "gpt-4",
|
|
Content: "hello world",
|
|
StopReason: llm.StopReasonToolUse,
|
|
}
|
|
client := NewWithGenerate(expected)
|
|
|
|
resp, err := client.Generate(context.Background(), llm.CompletionRequest{})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if resp.ID != "test_123" {
|
|
t.Errorf("expected ID 'test_123', got %q", resp.ID)
|
|
}
|
|
if resp.Content != "hello world" {
|
|
t.Errorf("expected 'hello world', got %q", resp.Content)
|
|
}
|
|
if resp.StopReason != llm.StopReasonToolUse {
|
|
t.Errorf("expected StopReasonToolUse, got %q", resp.StopReason)
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_Generate_Error(t *testing.T) {
|
|
client := &MockLLMClient{
|
|
GenerateFunc: func(ctx context.Context, req llm.CompletionRequest) (llm.CompletionResponse, error) {
|
|
return llm.CompletionResponse{}, fmt.Errorf("test error")
|
|
},
|
|
}
|
|
|
|
_, err := client.Generate(context.Background(), llm.CompletionRequest{})
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if err.Error() != "test error" {
|
|
t.Errorf("expected 'test error', got %q", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_Stream_Default(t *testing.T) {
|
|
client := New()
|
|
var chunks []string
|
|
for chunk, err := range client.Stream(context.Background(), llm.CompletionRequest{}) {
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
chunks = append(chunks, chunk.Delta)
|
|
}
|
|
if len(chunks) != 1 || chunks[0] != "default response" {
|
|
t.Errorf("expected single 'default response' chunk, got %v", chunks)
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_Stream_Custom(t *testing.T) {
|
|
expectedChunks := []llm.StreamChunk{
|
|
{Delta: "chunk1"},
|
|
{Delta: "chunk2", ToolCalls: []llm.ToolCall{{ID: "call_1"}}},
|
|
{FinishReason: "stop", Usage: llm.TokenUsage{InputTokens: 5, OutputTokens: 10}},
|
|
}
|
|
client := NewWithStream(expectedChunks)
|
|
|
|
var actualChunks []llm.StreamChunk
|
|
for chunk, err := range client.Stream(context.Background(), llm.CompletionRequest{}) {
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
actualChunks = append(actualChunks, chunk)
|
|
}
|
|
|
|
if len(actualChunks) != 3 {
|
|
t.Errorf("expected 3 chunks, got %d", len(actualChunks))
|
|
return
|
|
}
|
|
if actualChunks[0].Delta != "chunk1" {
|
|
t.Errorf("expected 'chunk1', got %q", actualChunks[0].Delta)
|
|
}
|
|
if len(actualChunks[1].ToolCalls) != 1 {
|
|
t.Errorf("expected 1 tool call, got %d", len(actualChunks[1].ToolCalls))
|
|
}
|
|
if actualChunks[2].FinishReason != "stop" {
|
|
t.Errorf("expected 'stop', got %q", actualChunks[2].FinishReason)
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_Name_Default(t *testing.T) {
|
|
client := New()
|
|
if name := client.Name(); name != "mock" {
|
|
t.Errorf("expected 'mock', got %q", name)
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_Name_Custom(t *testing.T) {
|
|
client := &MockLLMClient{
|
|
NameFunc: func() string { return "custom-model" },
|
|
}
|
|
if name := client.Name(); name != "custom-model" {
|
|
t.Errorf("expected 'custom-model', got %q", name)
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_Capabilities_Default(t *testing.T) {
|
|
client := New()
|
|
caps := client.Capabilities()
|
|
if !caps.SupportsTools {
|
|
t.Error("expected SupportsTools to be true")
|
|
}
|
|
if caps.SupportsVision {
|
|
t.Error("expected SupportsVision to be false")
|
|
}
|
|
if !caps.SupportsJSON {
|
|
t.Error("expected SupportsJSON to be true")
|
|
}
|
|
if caps.MaxContextWindow != 128000 {
|
|
t.Errorf("expected MaxContextWindow 128000, got %d", caps.MaxContextWindow)
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_Capabilities_Custom(t *testing.T) {
|
|
client := &MockLLMClient{
|
|
CapabilitiesFunc: func() llm.ProviderCapabilities {
|
|
return llm.ProviderCapabilities{
|
|
SupportsTools: false,
|
|
SupportsVision: true,
|
|
SupportsJSON: false,
|
|
MaxContextWindow: 32000,
|
|
}
|
|
},
|
|
}
|
|
caps := client.Capabilities()
|
|
if caps.SupportsTools {
|
|
t.Error("expected SupportsTools to be false")
|
|
}
|
|
if !caps.SupportsVision {
|
|
t.Error("expected SupportsVision to be true")
|
|
}
|
|
if caps.MaxContextWindow != 32000 {
|
|
t.Errorf("expected MaxContextWindow 32000, got %d", caps.MaxContextWindow)
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_MatchResponse(t *testing.T) {
|
|
client := NewWithMatch([]MatchResponse{
|
|
{Match: "hello", Response: "hi there!"},
|
|
{Match: "world", Response: "earth"},
|
|
{Match: "*", Response: "default"},
|
|
})
|
|
|
|
resp, _ := client.Generate(context.Background(), llm.CompletionRequest{
|
|
Messages: []llm.Message{{Role: llm.RoleUser, Content: "hello"}},
|
|
})
|
|
if resp.Content != "hi there!" {
|
|
t.Errorf("expected 'hi there!', got %q", resp.Content)
|
|
}
|
|
|
|
resp, _ = client.Generate(context.Background(), llm.CompletionRequest{
|
|
Messages: []llm.Message{{Role: llm.RoleUser, Content: "world"}},
|
|
})
|
|
if resp.Content != "earth" {
|
|
t.Errorf("expected 'earth', got %q", resp.Content)
|
|
}
|
|
|
|
resp, _ = client.Generate(context.Background(), llm.CompletionRequest{
|
|
Messages: []llm.Message{{Role: llm.RoleUser, Content: "anything else"}},
|
|
})
|
|
if resp.Content != "default" {
|
|
t.Errorf("expected 'default', got %q", resp.Content)
|
|
}
|
|
}
|
|
|
|
func TestMockLLMClient_MatchResponse_NoMatch(t *testing.T) {
|
|
client := NewWithMatch([]MatchResponse{
|
|
{Match: "exact", Response: "matched"},
|
|
})
|
|
|
|
resp, _ := client.Generate(context.Background(), llm.CompletionRequest{
|
|
Messages: []llm.Message{{Role: llm.RoleUser, Content: "no match here"}},
|
|
})
|
|
if resp.Content != "no match" {
|
|
t.Errorf("expected 'no match', got %q", resp.Content)
|
|
}
|
|
}
|
|
|
|
func TestMatchesAny(t *testing.T) {
|
|
if !matchesAny("hello", []llm.Message{
|
|
{Role: llm.RoleUser, Content: "say hello to me"},
|
|
}) {
|
|
t.Error("expected matchesAny to find 'hello' in message")
|
|
}
|
|
|
|
if matchesAny("goodbye", []llm.Message{
|
|
{Role: llm.RoleUser, Content: "say hello to me"},
|
|
}) {
|
|
t.Error("expected matchesAny to not find 'goodbye' in message")
|
|
}
|
|
}
|
|
|
|
func TestMatchesAny_Wildcard(t *testing.T) {
|
|
resp, _ := (&MockLLMClient{
|
|
GenerateFunc: func(ctx context.Context, req llm.CompletionRequest) (llm.CompletionResponse, error) {
|
|
for _, r := range []MatchResponse{{Match: "*", Response: "wildcard"}} {
|
|
if r.Match == "*" || matchesAny(r.Match, req.Messages) {
|
|
return llm.CompletionResponse{Content: r.Response}, nil
|
|
}
|
|
}
|
|
return llm.CompletionResponse{}, nil
|
|
},
|
|
}).Generate(context.Background(), llm.CompletionRequest{})
|
|
|
|
if resp.Content != "wildcard" {
|
|
t.Errorf("expected 'wildcard', got %q", resp.Content)
|
|
}
|
|
}
|