picoclaw/pkg/tools/freeride_test.go
2026-04-20 19:27:22 +02:00

316 lines
8.1 KiB
Go

package tools
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"github.com/sipeed/picoclaw/pkg/config"
)
func TestFreeRideTool_List(t *testing.T) {
// Mock OpenRouter API
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"data": []map[string]any{
{
"id": "google/gemini-pro-1.5",
"name": "Gemini Pro 1.5",
"context_length": 128000,
"pricing": map[string]string{
"prompt": "0",
"completion": "0",
},
"created": 1700000000,
"supported_parameters": []string{"tools"},
},
{
"id": "meta-llama/llama-3-8b",
"name": "Llama 3 8B",
"context_length": 8000,
"pricing": map[string]string{
"prompt": "0.0001",
"completion": "0.0001",
},
"created": 1700000000,
"supported_parameters": []string{"tools"},
},
},
})
}))
defer server.Close()
// Override default transport to use mock server
oldTransport := http.DefaultClient.Transport
http.DefaultClient.Transport = &mockTransport{server.URL}
defer func() { http.DefaultClient.Transport = oldTransport }()
tool := NewFreeRideTool("config.json", nil)
result := tool.Execute(context.Background(), map[string]any{
"command": "list",
})
if result.IsError {
t.Fatalf("Expected no error, got %s", result.ForLLM)
}
if result.Silent {
t.Errorf("Expected non-silent result")
}
output := result.ForLLM
if !contains(output, "Gemini Pro 1.5") {
t.Errorf("Expected Gemini Pro 1.5 in output, got %s", output)
}
if contains(output, "Llama 3 8B") {
t.Errorf("Did not expect paid model Llama 3 8B in output, got %s", output)
}
}
func TestFreeRideTool_Auto(t *testing.T) {
os.Setenv("OPENROUTER_API_KEY", "sk-test-key")
defer os.Unsetenv("OPENROUTER_API_KEY")
tempDir, err := os.MkdirTemp("", "freeride-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
configPath := filepath.Join(tempDir, "config.json")
initialCfg := &config.Config{
ModelList: []*config.ModelConfig{},
}
initialCfg.Agents.Defaults.ModelName = "existing-model"
if err := config.SaveConfig(configPath, initialCfg); err != nil {
t.Fatalf("failed to save initial config: %v", err)
}
// Mock OpenRouter API
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"data": []map[string]any{
{
"id": "google/gemini-pro-1.5",
"name": "Gemini Pro 1.5",
"context_length": 128000,
"pricing": map[string]string{
"prompt": "0",
"completion": "0",
},
"created": 1700000000,
"supported_parameters": []string{"tools"},
},
},
})
}))
defer server.Close()
oldTransport := http.DefaultClient.Transport
http.DefaultClient.Transport = &mockTransport{server.URL}
defer func() { http.DefaultClient.Transport = oldTransport }()
var reloadCalled bool
reloadFunc := func() error {
reloadCalled = true
return nil
}
tool := NewFreeRideTool(configPath, reloadFunc)
result := tool.Execute(context.Background(), map[string]any{
"command": "auto",
})
if result.IsError {
t.Fatalf("Expected no error, got %s", result.ForLLM)
}
if !reloadCalled {
t.Errorf("Expected reloadFunc to be called")
}
// Verify config
cfg, err := config.LoadConfig(configPath)
if err != nil {
t.Fatalf("failed to load updated config: %v", err)
}
if len(cfg.ModelList) != 1 {
t.Errorf("Expected 1 model in ModelList, got %d", len(cfg.ModelList))
}
if cfg.ModelList[0].ModelName != "google-gemini-pro-1.5" {
t.Errorf("Expected model name google-gemini-pro-1.5, got %s", cfg.ModelList[0].ModelName)
}
if len(cfg.Agents.Defaults.ModelFallbacks) != 1 {
t.Errorf("Expected 1 fallback, got %d", len(cfg.Agents.Defaults.ModelFallbacks))
}
}
func TestFreeRideTool_SetTimeout(t *testing.T) {
tempDir, err := os.MkdirTemp("", "freeride-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
configPath := filepath.Join(tempDir, "config.json")
initialCfg := &config.Config{
ModelList: []*config.ModelConfig{
{
ModelName: "google-gemini-pro-1.5",
Model: "google/gemini-pro-1.5",
Protocol: "openrouter",
},
{
ModelName: "meta-llama-3-8b",
Model: "meta/llama-3-8b",
Protocol: "openrouter",
},
{
ModelName: "gpt-4o",
Model: "openai/gpt-4o",
Protocol: "openai",
},
},
}
initialCfg.Agents.Defaults.ModelName = "gpt-4o"
if err := config.SaveConfig(configPath, initialCfg); err != nil {
t.Fatalf("failed to save initial config: %v", err)
}
var reloadCalled bool
reloadFunc := func() error {
reloadCalled = true
return nil
}
tool := NewFreeRideTool(configPath, reloadFunc)
result := tool.Execute(context.Background(), map[string]any{
"command": "settimeout",
"timeout": 180,
})
if result.IsError {
t.Fatalf("Expected no error, got %s", result.ForLLM)
}
if !reloadCalled {
t.Errorf("Expected reloadFunc to be called")
}
// Verify config
cfg, err := config.LoadConfig(configPath)
if err != nil {
t.Fatalf("failed to load updated config: %v", err)
}
// Should have updated 2 openrouter models
if cfg.ModelList[0].RequestTimeout != 180 {
t.Errorf("Expected timeout 180 for google-gemini-pro-1.5, got %d", cfg.ModelList[0].RequestTimeout)
}
if cfg.ModelList[1].RequestTimeout != 180 {
t.Errorf("Expected timeout 180 for meta-llama-3-8b, got %d", cfg.ModelList[1].RequestTimeout)
}
// openai model should NOT be updated
if cfg.ModelList[2].RequestTimeout != 0 {
t.Errorf("Expected timeout 0 for gpt-4o (non-openrouter), got %d", cfg.ModelList[2].RequestTimeout)
}
}
func TestFreeRideTool_SetTimeout_NoOpenRouterModels(t *testing.T) {
tempDir, err := os.MkdirTemp("", "freeride-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
configPath := filepath.Join(tempDir, "config.json")
initialCfg := &config.Config{
ModelList: []*config.ModelConfig{
{
ModelName: "gpt-4o",
Model: "openai/gpt-4o",
Protocol: "openai",
},
},
}
if err := config.SaveConfig(configPath, initialCfg); err != nil {
t.Fatalf("failed to save initial config: %v", err)
}
tool := NewFreeRideTool(configPath, nil)
result := tool.Execute(context.Background(), map[string]any{
"command": "settimeout",
"timeout": 180,
})
if !result.IsError {
t.Fatalf("Expected error when no OpenRouter models, got success")
}
if !contains(result.ForLLM, "no OpenRouter models") {
t.Errorf("Expected error message about no OpenRouter models, got %s", result.ForLLM)
}
}
func TestFreeRideTool_SetTimeout_MinimumTooLow(t *testing.T) {
tempDir, err := os.MkdirTemp("", "freeride-test")
if err != nil {
t.Fatalf("failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
configPath := filepath.Join(tempDir, "config.json")
initialCfg := &config.Config{
ModelList: []*config.ModelConfig{
{
ModelName: "google-gemini-pro-1.5",
Model: "google/gemini-pro-1.5",
Protocol: "openrouter",
},
},
}
if err := config.SaveConfig(configPath, initialCfg); err != nil {
t.Fatalf("failed to save initial config: %v", err)
}
tool := NewFreeRideTool(configPath, nil)
result := tool.Execute(context.Background(), map[string]any{
"command": "settimeout",
"timeout": 20, // too low
})
if !result.IsError {
t.Fatalf("Expected error when timeout < 30, got success")
}
if !contains(result.ForLLM, "at least 30") {
t.Errorf("Expected error message about minimum 30 seconds, got %s", result.ForLLM)
}
}
type mockTransport struct {
url string
}
func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
newReq, _ := http.NewRequest(req.Method, m.url, req.Body)
return http.DefaultTransport.RoundTrip(newReq)
}
func contains(s, substr string) bool {
return strings.Contains(s, substr)
}