356 lines
9.1 KiB
Go
356 lines
9.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)
|
|
}
|
|
}
|
|
|
|
func TestFreeRideTool_Clear(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "freeride-clear-test")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
configPath := filepath.Join(tempDir, "config.json")
|
|
cooldownPath := filepath.Join(tempDir, "cooldowns.json")
|
|
|
|
// Create a dummy cooldown file
|
|
if err := os.WriteFile(cooldownPath, []byte("{}"), 0644); err != nil {
|
|
t.Fatalf("failed to create dummy cooldown file: %v", err)
|
|
}
|
|
|
|
var reloadCalled bool
|
|
reloadFunc := func() error {
|
|
reloadCalled = true
|
|
return nil
|
|
}
|
|
|
|
tool := NewFreeRideTool(configPath, cooldownPath, reloadFunc)
|
|
result := tool.Execute(context.Background(), map[string]any{
|
|
"command": "clear",
|
|
})
|
|
|
|
if result.IsError {
|
|
t.Fatalf("Expected no error, got %s", result.ForLLM)
|
|
}
|
|
|
|
if !reloadCalled {
|
|
t.Errorf("Expected reloadFunc to be called")
|
|
}
|
|
|
|
// Verify file is gone
|
|
if _, err := os.Stat(cooldownPath); !os.IsNotExist(err) {
|
|
t.Errorf("Expected cooldown file to be deleted, but it still exists")
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|