678 lines
22 KiB
Plaintext
678 lines
22 KiB
Plaintext
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// ========== Create ==========
|
|
|
|
func TestCaptainCustomToolService_Create_成功(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
req := &CreateCustomToolRequest{
|
|
Title: "天气查询",
|
|
Slug: "weather-query",
|
|
Description: "查询天气信息",
|
|
EndpointURL: "https://api.weather.com/v1/current",
|
|
HTTPMethod: "POST",
|
|
AuthType: "none",
|
|
}
|
|
|
|
tool, err := svc.Create(context.Background(), account.ID, req)
|
|
require.NoError(t, err)
|
|
assert.NotZero(t, tool.ID)
|
|
assert.Equal(t, account.ID, tool.AccountID)
|
|
assert.Equal(t, "天气查询", tool.Title)
|
|
assert.Equal(t, "weather-query", tool.Slug)
|
|
assert.Equal(t, "POST", tool.HTTPMethod)
|
|
assert.Equal(t, model.ToolAuthTypeNone, tool.AuthType)
|
|
assert.True(t, tool.Enabled)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Create_默认值(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// 不设置 HTTPMethod 和 AuthType,应使用默认值
|
|
req := &CreateCustomToolRequest{
|
|
Title: "默认工具",
|
|
Slug: "default-tool",
|
|
EndpointURL: "https://api.example.com",
|
|
}
|
|
|
|
tool, err := svc.Create(context.Background(), account.ID, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "GET", tool.HTTPMethod) // 默认 GET
|
|
assert.Equal(t, model.ToolAuthTypeNone, tool.AuthType) // 默认 none
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Create_带认证配置(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
authConfig := json.RawMessage(`{"username":"user1","password":"pass1"}`)
|
|
req := &CreateCustomToolRequest{
|
|
Title: "带认证工具",
|
|
Slug: "auth-tool",
|
|
EndpointURL: "https://api.example.com/auth",
|
|
HTTPMethod: "POST",
|
|
AuthType: "basic",
|
|
AuthConfig: authConfig,
|
|
}
|
|
|
|
tool, err := svc.Create(context.Background(), account.ID, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, model.ToolAuthTypeBasic, tool.AuthType)
|
|
assert.Equal(t, authConfig, tool.AuthConfig)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Create_带模板(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
reqTemplate := `{"query":"{{.keyword}}","location":"{{.location}}"}`
|
|
respTemplate := `{"result":"{{.data.answer}}"}`
|
|
paramSchema := json.RawMessage(`{"type":"object","properties":{"keyword":{"type":"string"},"location":{"type":"string"}}}`)
|
|
|
|
req := &CreateCustomToolRequest{
|
|
Title: "模板工具",
|
|
Slug: "template-tool",
|
|
EndpointURL: "https://api.example.com/search",
|
|
RequestTemplate: reqTemplate,
|
|
ResponseTemplate: respTemplate,
|
|
ParamSchema: paramSchema,
|
|
}
|
|
|
|
tool, err := svc.Create(context.Background(), account.ID, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, reqTemplate, tool.RequestTemplate)
|
|
assert.Equal(t, respTemplate, tool.ResponseTemplate)
|
|
assert.Equal(t, paramSchema, tool.ParamSchema)
|
|
}
|
|
|
|
// ========== Get ==========
|
|
|
|
func TestCaptainCustomToolService_Get_成功(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
tool := createTestCaptainCustomTool(t, db, account.ID)
|
|
|
|
result, err := svc.Get(context.Background(), tool.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tool.ID, result.ID)
|
|
assert.Equal(t, tool.Title, result.Title)
|
|
assert.Equal(t, tool.Slug, result.Slug)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Get_不存在(t *testing.T) {
|
|
_, _, svc := setupCaptainCustomToolService(t)
|
|
|
|
result, err := svc.Get(context.Background(), 9999)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
// ========== Update ==========
|
|
|
|
func TestCaptainCustomToolService_Update_部分更新(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
tool := createTestCaptainCustomTool(t, db, account.ID)
|
|
|
|
newTitle := "更新后的工具"
|
|
req := &UpdateCustomToolRequest{
|
|
Title: newTitle,
|
|
}
|
|
|
|
result, err := svc.Update(context.Background(), tool.ID, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, newTitle, result.Title)
|
|
// 其他字段保持不变
|
|
assert.Equal(t, tool.Slug, result.Slug)
|
|
assert.Equal(t, tool.EndpointURL, result.EndpointURL)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Update_禁用工具(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
tool := createTestCaptainCustomTool(t, db, account.ID)
|
|
|
|
disabled := false
|
|
req := &UpdateCustomToolRequest{
|
|
Enabled: &disabled,
|
|
}
|
|
|
|
result, err := svc.Update(context.Background(), tool.ID, req)
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Enabled)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Update_启用工具(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
// 创建一个已禁用的工具
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.Enabled = false
|
|
})
|
|
|
|
enabled := true
|
|
req := &UpdateCustomToolRequest{
|
|
Enabled: &enabled,
|
|
}
|
|
|
|
result, err := svc.Update(context.Background(), tool.ID, req)
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Enabled)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Update_更新认证配置(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
tool := createTestCaptainCustomTool(t, db, account.ID)
|
|
|
|
newAuthConfig := json.RawMessage(`{"token":"new-bearer-token"}`)
|
|
req := &UpdateCustomToolRequest{
|
|
AuthType: "bearer",
|
|
AuthConfig: newAuthConfig,
|
|
}
|
|
|
|
result, err := svc.Update(context.Background(), tool.ID, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, model.ToolAuthTypeBearer, result.AuthType)
|
|
assert.Equal(t, newAuthConfig, result.AuthConfig)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Update_不存在(t *testing.T) {
|
|
_, _, svc := setupCaptainCustomToolService(t)
|
|
|
|
req := &UpdateCustomToolRequest{Title: "不存在的工具"}
|
|
result, err := svc.Update(context.Background(), 9999, req)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Update_忽略nullAuthConfig(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
authConfig := json.RawMessage(`{"token":"original-token"}`)
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.AuthType = model.ToolAuthTypeBearer
|
|
t.AuthConfig = authConfig
|
|
})
|
|
|
|
// 传入 "null" JSON,不应覆盖原值
|
|
req := &UpdateCustomToolRequest{
|
|
AuthConfig: json.RawMessage(`null`),
|
|
}
|
|
|
|
result, err := svc.Update(context.Background(), tool.ID, req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, authConfig, result.AuthConfig) // 保持原值
|
|
}
|
|
|
|
// ========== Delete ==========
|
|
|
|
func TestCaptainCustomToolService_Delete_成功(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
tool := createTestCaptainCustomTool(t, db, account.ID)
|
|
|
|
err := svc.Delete(context.Background(), tool.ID)
|
|
require.NoError(t, err)
|
|
|
|
// 验证已删除(软删除后 GetByID 应返回错误)
|
|
result, err := svc.Get(context.Background(), tool.ID)
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_Delete_不存在(t *testing.T) {
|
|
_, _, svc := setupCaptainCustomToolService(t)
|
|
|
|
err := svc.Delete(context.Background(), 9999)
|
|
// GORM Delete 对不存在的记录不返回错误(匹配 0 行)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// ========== List ==========
|
|
|
|
func TestCaptainCustomToolService_List_成功(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// 创建多个工具
|
|
for i := 0; i < 5; i++ {
|
|
createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.Slug = fmt.Sprintf("tool-%d", i)
|
|
t.Title = fmt.Sprintf("工具%d", i)
|
|
})
|
|
}
|
|
|
|
tools, count, err := svc.List(context.Background(), account.ID, 0, 10)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(5), count)
|
|
assert.Len(t, tools, 5)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_List_分页(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.Slug = fmt.Sprintf("page-tool-%d", i)
|
|
t.Title = fmt.Sprintf("分页工具%d", i)
|
|
})
|
|
}
|
|
|
|
// 第二页,每页2条,offset=2
|
|
tools, count, err := svc.List(context.Background(), account.ID, 2, 2)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(5), count) // 总数不变
|
|
assert.Len(t, tools, 2) // 只返回2条
|
|
}
|
|
|
|
func TestCaptainCustomToolService_List_空列表(t *testing.T) {
|
|
_, _, svc := setupCaptainCustomToolService(t)
|
|
|
|
tools, count, err := svc.List(context.Background(), 9999, 0, 10)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), count)
|
|
assert.Empty(t, tools)
|
|
}
|
|
|
|
// ========== ExecuteTool ==========
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_成功GET请求(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// 创建测试 HTTP 服务器
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "GET", r.Method)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{"result": "success"})
|
|
}))
|
|
defer server.Close()
|
|
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = server.URL
|
|
t.HTTPMethod = "GET"
|
|
t.AuthType = model.ToolAuthTypeNone
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_成功POST请求(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "POST", r.Method)
|
|
assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{"status": "ok"})
|
|
}))
|
|
defer server.Close()
|
|
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = server.URL
|
|
t.HTTPMethod = "POST"
|
|
t.AuthType = model.ToolAuthTypeNone
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{
|
|
"query": "test",
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_禁用工具(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// 先创建工具,然后通过 service Update 禁用它
|
|
tool := createTestCaptainCustomTool(t, db, account.ID)
|
|
disabled := false
|
|
_, err := svc.Update(context.Background(), tool.ID, &UpdateCustomToolRequest{Enabled: &disabled})
|
|
require.NoError(t, err)
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{})
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
assert.Contains(t, err.Error(), "disabled")
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_不存在(t *testing.T) {
|
|
_, _, svc := setupCaptainCustomToolService(t)
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), 9999, map[string]interface{}{})
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
assert.Contains(t, err.Error(), "not found")
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_Bearer认证(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
expectedToken := "test-bearer-token-123"
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
authHeader := r.Header.Get("Authorization")
|
|
assert.Equal(t, "Bearer "+expectedToken, authHeader)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{"auth": "ok"})
|
|
}))
|
|
defer server.Close()
|
|
|
|
authConfig := json.RawMessage(fmt.Sprintf(`{"token":"%s"}`, expectedToken))
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = server.URL
|
|
t.HTTPMethod = "GET"
|
|
t.AuthType = model.ToolAuthTypeBearer
|
|
t.AuthConfig = authConfig
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_Basic认证(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
username, password, ok := r.BasicAuth()
|
|
assert.True(t, ok)
|
|
assert.Equal(t, "testuser", username)
|
|
assert.Equal(t, "testpass", password)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{"auth": "basic_ok"})
|
|
}))
|
|
defer server.Close()
|
|
|
|
authConfig := json.RawMessage(`{"username":"testuser","password":"testpass"}`)
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = server.URL
|
|
t.HTTPMethod = "GET"
|
|
t.AuthType = model.ToolAuthTypeBasic
|
|
t.AuthConfig = authConfig
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_ApiKey认证(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
apiKey := r.Header.Get("X-API-Key")
|
|
assert.Equal(t, "my-secret-key", apiKey)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{"auth": "apikey_ok"})
|
|
}))
|
|
defer server.Close()
|
|
|
|
authConfig := json.RawMessage(`{"key":"X-API-Key","value":"my-secret-key"}`)
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = server.URL
|
|
t.HTTPMethod = "GET"
|
|
t.AuthType = model.ToolAuthTypeApiKey
|
|
t.AuthConfig = authConfig
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_ApiKey自定义Header(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
customHeader := "X-Custom-Token"
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
val := r.Header.Get(customHeader)
|
|
assert.Equal(t, "custom-value-xyz", val)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]string{"auth": "custom_ok"})
|
|
}))
|
|
defer server.Close()
|
|
|
|
authConfig := json.RawMessage(fmt.Sprintf(`{"key":"%s","value":"custom-value-xyz","header":"%s"}`, customHeader, customHeader))
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = server.URL
|
|
t.HTTPMethod = "GET"
|
|
t.AuthType = model.ToolAuthTypeApiKey
|
|
t.AuthConfig = authConfig
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_带请求模板(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "POST", r.Method)
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(map[string]interface{}{"answer": "42"})
|
|
}))
|
|
defer server.Close()
|
|
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = server.URL
|
|
t.HTTPMethod = "POST"
|
|
t.AuthType = model.ToolAuthTypeNone
|
|
t.RequestTemplate = `{"query":"{{.keyword}}"}`
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{
|
|
"keyword": "golang",
|
|
})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_带响应模板(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"data":{"answer":"42","confidence":"high"}}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = server.URL
|
|
t.HTTPMethod = "GET"
|
|
t.AuthType = model.ToolAuthTypeNone
|
|
t.ResponseTemplate = "{\"extracted\":\"{{.data.answer}}\",\"conf\":\"{{.data.confidence}}\"}"
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{})
|
|
require.NoError(t, err)
|
|
assert.True(t, result.Success)
|
|
// 响应模板应被渲染
|
|
assert.NotNil(t, result.Data)
|
|
}
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_服务端错误(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(`{"error":"internal server error"}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = server.URL
|
|
t.HTTPMethod = "GET"
|
|
t.AuthType = model.ToolAuthTypeNone
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{})
|
|
require.NoError(t, err) // 不返回 Go error,但 Success=false
|
|
assert.False(t, result.Success)
|
|
}
|
|
|
|
// ========== buildRequestBody ==========
|
|
|
|
func TestBuildRequestBody_空模板(t *testing.T) {
|
|
params := map[string]interface{}{"key": "value"}
|
|
body, err := buildRequestBody("", params)
|
|
require.NoError(t, err)
|
|
|
|
var parsed map[string]interface{}
|
|
require.NoError(t, json.Unmarshal(body, &parsed))
|
|
assert.Equal(t, "value", parsed["key"])
|
|
}
|
|
|
|
func TestBuildRequestBody_模板渲染(t *testing.T) {
|
|
params := map[string]interface{}{"keyword": "weather", "location": "Beijing"}
|
|
body, err := buildRequestBody(`{"query":"{{.keyword}}","loc":"{{.location}}"}`, params)
|
|
require.NoError(t, err)
|
|
|
|
var parsed map[string]interface{}
|
|
require.NoError(t, json.Unmarshal(body, &parsed))
|
|
assert.Equal(t, "weather", parsed["query"])
|
|
assert.Equal(t, "Beijing", parsed["loc"])
|
|
}
|
|
|
|
func TestBuildRequestBody_模板解析错误(t *testing.T) {
|
|
params := map[string]interface{}{"key": "value"}
|
|
_, err := buildRequestBody(`{{.invalid`, params)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "parse request template")
|
|
}
|
|
|
|
// ========== parseResponseTemplate ==========
|
|
|
|
func TestParseResponseTemplate_成功(t *testing.T) {
|
|
rawBody := []byte(`{"data":{"answer":"42"}}`)
|
|
result, err := parseResponseTemplate(`{"result":"{{.data.answer}}"}`, rawBody)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, result)
|
|
}
|
|
|
|
func TestParseResponseTemplate_非JSONBody(t *testing.T) {
|
|
rawBody := []byte(`not json at all`)
|
|
result, err := parseResponseTemplate(`raw={{.raw}}`, rawBody)
|
|
require.NoError(t, err)
|
|
// 当 body 非 JSON 时,会放入 {"raw": "not json at all"},模板渲染后为 "raw=not json at all"
|
|
assert.Equal(t, "raw=not json at all", result)
|
|
}
|
|
|
|
func TestParseResponseTemplate_无效模板(t *testing.T) {
|
|
rawBody := []byte(`{"data":"test"}`)
|
|
_, err := parseResponseTemplate(`{{.invalid`, rawBody)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "parse response template")
|
|
}
|
|
|
|
// ========== applyAuth ==========
|
|
|
|
func TestApplyAuth_无认证(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "http://example.com", nil)
|
|
tool := &model.CaptainCustomTool{AuthType: model.ToolAuthTypeNone}
|
|
|
|
err := applyAuth(req, tool)
|
|
require.NoError(t, err)
|
|
// 不应设置任何 Authorization header
|
|
assert.Empty(t, req.Header.Get("Authorization"))
|
|
assert.Empty(t, req.Header.Get("X-API-Key"))
|
|
}
|
|
|
|
func TestApplyAuth_不支持的认证类型(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "http://example.com", nil)
|
|
tool := &model.CaptainCustomTool{
|
|
AuthType: model.ToolAuthType("unknown"),
|
|
AuthConfig: json.RawMessage(`{}`),
|
|
}
|
|
|
|
err := applyAuth(req, tool)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "unsupported auth type")
|
|
}
|
|
|
|
func TestApplyAuth_Basic认证解析失败(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "http://example.com", nil)
|
|
tool := &model.CaptainCustomTool{
|
|
AuthType: model.ToolAuthTypeBasic,
|
|
AuthConfig: json.RawMessage(`invalid-json`),
|
|
}
|
|
|
|
err := applyAuth(req, tool)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "parse basic auth config")
|
|
}
|
|
|
|
func TestApplyAuth_Bearer认证解析失败(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "http://example.com", nil)
|
|
tool := &model.CaptainCustomTool{
|
|
AuthType: model.ToolAuthTypeBearer,
|
|
AuthConfig: json.RawMessage(`invalid-json`),
|
|
}
|
|
|
|
err := applyAuth(req, tool)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "parse bearer auth config")
|
|
}
|
|
|
|
func TestApplyAuth_ApiKey认证解析失败(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "http://example.com", nil)
|
|
tool := &model.CaptainCustomTool{
|
|
AuthType: model.ToolAuthTypeApiKey,
|
|
AuthConfig: json.RawMessage(`invalid-json`),
|
|
}
|
|
|
|
err := applyAuth(req, tool)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "parse api_key auth config")
|
|
}
|
|
|
|
// ========== ExecuteTool HTTP 网络错误 ==========
|
|
|
|
func TestCaptainCustomToolService_ExecuteTool_网络错误(t *testing.T) {
|
|
db, _, svc := setupCaptainCustomToolService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// 使用无效 URL 触发网络错误
|
|
tool := createTestCaptainCustomTool(t, db, account.ID, func(t *model.CaptainCustomTool) {
|
|
t.EndpointURL = "http://127.0.0.1:0/invalid" // 端口0无效
|
|
t.HTTPMethod = "GET"
|
|
t.AuthType = model.ToolAuthTypeNone
|
|
})
|
|
|
|
result, err := svc.ExecuteTool(context.Background(), tool.ID, map[string]interface{}{})
|
|
// 网络错误不返回 Go error,而是返回 Success=false 的结果
|
|
require.NoError(t, err)
|
|
assert.False(t, result.Success)
|
|
assert.NotEmpty(t, result.Error)
|
|
} |