Files
gochat/internal/service/captain_custom_tool_service_test.go_BAK
T
2026-06-04 15:44:48 +08:00

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)
}