524 lines
16 KiB
Plaintext
524 lines
16 KiB
Plaintext
package service
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"fmt"
|
||
"testing"
|
||
|
||
"github.com/gochat/gochat/internal/llm"
|
||
"github.com/gochat/gochat/internal/model"
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
)
|
||
|
||
// ========== Create ==========
|
||
|
||
func TestCaptainAssistantService_Create_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
|
||
req := &CreateAssistantRequest{
|
||
Name: "客服助手",
|
||
Description: "处理客户咨询",
|
||
}
|
||
|
||
assistant, err := svc.Create(context.Background(), account.ID, req)
|
||
require.NoError(t, err)
|
||
assert.NotZero(t, assistant.ID)
|
||
assert.Equal(t, account.ID, assistant.AccountID)
|
||
assert.Equal(t, "客服助手", assistant.Name)
|
||
assert.Equal(t, "处理客户咨询", assistant.Description)
|
||
assert.Equal(t, model.AssistantStatusActive, assistant.Status)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Create_默认Config(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
|
||
req := &CreateAssistantRequest{
|
||
Name: "默认助手",
|
||
}
|
||
|
||
assistant, err := svc.Create(context.Background(), account.ID, req)
|
||
require.NoError(t, err)
|
||
assert.NotZero(t, assistant.ID)
|
||
// 默认config不应为空或null
|
||
assert.NotEqual(t, "null", string(assistant.Config))
|
||
assert.True(t, len(assistant.Config) > 0)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Create_自定义Config(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
|
||
config := json.RawMessage(`{"temperature":0.5,"model":"gpt-4","product_name":"我的产品"}`)
|
||
req := &CreateAssistantRequest{
|
||
Name: "自定义助手",
|
||
Config: config,
|
||
}
|
||
|
||
assistant, err := svc.Create(context.Background(), account.ID, req)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, config, assistant.Config)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Create_自定义Guardrails(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
|
||
guardrails := json.RawMessage(`["禁止讨论政治","保持礼貌"]`)
|
||
req := &CreateAssistantRequest{
|
||
Name: "受限助手",
|
||
Guardrails: guardrails,
|
||
}
|
||
|
||
assistant, err := svc.Create(context.Background(), account.ID, req)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, guardrails, assistant.Guardrails)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Create_自定义ResponseGuidelines(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
|
||
guidelines := json.RawMessage(`{"style":"professional","language":"zh"}`)
|
||
req := &CreateAssistantRequest{
|
||
Name: "指南助手",
|
||
ResponseGuidelines: guidelines,
|
||
}
|
||
|
||
assistant, err := svc.Create(context.Background(), account.ID, req)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, guidelines, assistant.ResponseGuidelines)
|
||
}
|
||
|
||
// ========== Get ==========
|
||
|
||
func TestCaptainAssistantService_Get_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
result, err := svc.Get(context.Background(), assistant.ID)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, assistant.ID, result.ID)
|
||
assert.Equal(t, assistant.Name, result.Name)
|
||
assert.Equal(t, account.ID, result.AccountID)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Get_不存在(t *testing.T) {
|
||
_, _, svc := setupCaptainAssistantService(t)
|
||
|
||
result, err := svc.Get(context.Background(), 9999)
|
||
assert.Error(t, err)
|
||
assert.Nil(t, result)
|
||
}
|
||
|
||
// ========== Update ==========
|
||
|
||
func TestCaptainAssistantService_Update_成功修改名称(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
req := &UpdateAssistantRequest{
|
||
Name: "新名称助手",
|
||
}
|
||
|
||
result, err := svc.Update(context.Background(), assistant.ID, req)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, "新名称助手", result.Name)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Update_修改Status(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
req := &UpdateAssistantRequest{
|
||
Status: "archived",
|
||
}
|
||
|
||
result, err := svc.Update(context.Background(), assistant.ID, req)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, model.AssistantStatusArchived, result.Status)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Update_修改Config(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
newConfig := json.RawMessage(`{"temperature":0.9,"model":"gpt-3.5-turbo"}`)
|
||
req := &UpdateAssistantRequest{
|
||
Config: newConfig,
|
||
}
|
||
|
||
result, err := svc.Update(context.Background(), assistant.ID, req)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, newConfig, result.Config)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Update_不存在ID(t *testing.T) {
|
||
_, _, svc := setupCaptainAssistantService(t)
|
||
|
||
req := &UpdateAssistantRequest{
|
||
Name: "不存在",
|
||
}
|
||
|
||
result, err := svc.Update(context.Background(), 9999, req)
|
||
assert.Error(t, err)
|
||
assert.Nil(t, result)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Update_空字段不修改(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID, func(a *model.CaptainAssistant) {
|
||
a.Description = "原始描述"
|
||
})
|
||
|
||
req := &UpdateAssistantRequest{} // 所有字段为空
|
||
|
||
result, err := svc.Update(context.Background(), assistant.ID, req)
|
||
require.NoError(t, err)
|
||
// Name/Description 未变
|
||
assert.Equal(t, assistant.Name, result.Name)
|
||
assert.Equal(t, "原始描述", result.Description)
|
||
}
|
||
|
||
// ========== Delete ==========
|
||
|
||
func TestCaptainAssistantService_Delete_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
err := svc.Delete(context.Background(), assistant.ID)
|
||
require.NoError(t, err)
|
||
|
||
// 验证已删除
|
||
result, err := svc.Get(context.Background(), assistant.ID)
|
||
assert.Error(t, err)
|
||
assert.Nil(t, result)
|
||
}
|
||
|
||
func TestCaptainAssistantService_Delete_不存在ID(t *testing.T) {
|
||
_, _, svc := setupCaptainAssistantService(t)
|
||
|
||
err := svc.Delete(context.Background(), 9999)
|
||
// GORM delete 不存在记录不报错(0 rows affected)
|
||
assert.NoError(t, err)
|
||
}
|
||
|
||
// ========== List ==========
|
||
|
||
func TestCaptainAssistantService_List_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
createTestCaptainAssistant(t, db, account.ID, func(a *model.CaptainAssistant) { a.Name = "助手1" })
|
||
createTestCaptainAssistant(t, db, account.ID, func(a *model.CaptainAssistant) { a.Name = "助手2" })
|
||
createTestCaptainAssistant(t, db, account.ID, func(a *model.CaptainAssistant) { a.Name = "助手3" })
|
||
|
||
assistants, count, err := svc.List(context.Background(), account.ID, 0, 10)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, int64(3), count)
|
||
assert.Len(t, assistants, 3)
|
||
}
|
||
|
||
func TestCaptainAssistantService_List_分页(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
for i := 0; i < 5; i++ {
|
||
createTestCaptainAssistant(t, db, account.ID, func(a *model.CaptainAssistant) {
|
||
a.Name = "助手" + string(rune('A'+i))
|
||
})
|
||
}
|
||
|
||
// offset=2, limit=2
|
||
assistants, count, err := svc.List(context.Background(), account.ID, 2, 2)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, int64(5), count) // 总数
|
||
assert.Len(t, assistants, 2) // 本页
|
||
}
|
||
|
||
func TestCaptainAssistantService_List_空列表(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
|
||
assistants, count, err := svc.List(context.Background(), account.ID, 0, 10)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, int64(0), count)
|
||
assert.Len(t, assistants, 0)
|
||
}
|
||
|
||
// ========== GetConfig ==========
|
||
|
||
func TestCaptainAssistantService_GetConfig_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID, func(a *model.CaptainAssistant) {
|
||
cfg := &model.AssistantConfig{
|
||
Temperature: 0.5,
|
||
Model: "gpt-4",
|
||
ProductName: "测试产品",
|
||
}
|
||
data, _ := json.Marshal(cfg)
|
||
a.Config = data
|
||
})
|
||
// 先保存带config的assistant
|
||
if err := db.Save(assistant).Error; err != nil {
|
||
t.Fatalf("保存助手失败: %v", err)
|
||
}
|
||
|
||
cfg, err := svc.GetConfig(context.Background(), assistant.ID)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, 0.5, cfg.Temperature)
|
||
assert.Equal(t, "gpt-4", cfg.Model)
|
||
assert.Equal(t, "测试产品", cfg.ProductName)
|
||
}
|
||
|
||
func TestCaptainAssistantService_GetConfig_不存在ID(t *testing.T) {
|
||
_, _, svc := setupCaptainAssistantService(t)
|
||
|
||
cfg, err := svc.GetConfig(context.Background(), 9999)
|
||
assert.Error(t, err)
|
||
assert.Nil(t, cfg)
|
||
}
|
||
|
||
// ========== SetConfig ==========
|
||
|
||
func TestCaptainAssistantService_SetConfig_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
newCfg := &model.AssistantConfig{
|
||
Temperature: 0.3,
|
||
Model: "gpt-4-turbo",
|
||
ProductName: "新产品",
|
||
}
|
||
|
||
err := svc.SetConfig(context.Background(), assistant.ID, newCfg)
|
||
require.NoError(t, err)
|
||
|
||
// 验证config已更新
|
||
result, err := svc.GetConfig(context.Background(), assistant.ID)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, 0.3, result.Temperature)
|
||
assert.Equal(t, "gpt-4-turbo", result.Model)
|
||
assert.Equal(t, "新产品", result.ProductName)
|
||
}
|
||
|
||
func TestCaptainAssistantService_SetConfig_不存在ID(t *testing.T) {
|
||
_, _, svc := setupCaptainAssistantService(t)
|
||
|
||
newCfg := &model.AssistantConfig{Temperature: 0.1}
|
||
err := svc.SetConfig(context.Background(), 9999, newCfg)
|
||
assert.Error(t, err)
|
||
}
|
||
|
||
// ========== AssociateInbox ==========
|
||
|
||
func TestCaptainAssistantService_AssociateInbox_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
err := svc.AssociateInbox(context.Background(), assistant.ID, 100, account.ID)
|
||
require.NoError(t, err)
|
||
|
||
// 验证inbox已关联
|
||
var inboxes []model.CaptainInbox
|
||
err = db.Where("captain_assistant_id = ? AND inbox_id = ?", assistant.ID, 100).Find(&inboxes).Error
|
||
require.NoError(t, err)
|
||
assert.Len(t, inboxes, 1)
|
||
assert.Equal(t, assistant.ID, inboxes[0].AssistantID)
|
||
assert.Equal(t, uint(100), inboxes[0].InboxID)
|
||
assert.Equal(t, account.ID, inboxes[0].AccountID)
|
||
}
|
||
|
||
func TestCaptainAssistantService_AssociateInbox_助手不属于账户(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
// 创建一个不属于account的assistant
|
||
otherAccount := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, otherAccount.ID)
|
||
|
||
err := svc.AssociateInbox(context.Background(), assistant.ID, 100, account.ID)
|
||
assert.Error(t, err)
|
||
assert.Contains(t, err.Error(), "does not belong to account")
|
||
}
|
||
|
||
func TestCaptainAssistantService_AssociateInbox_助手不存在(t *testing.T) {
|
||
_, _, svc := setupCaptainAssistantService(t)
|
||
|
||
err := svc.AssociateInbox(context.Background(), 9999, 100, 1)
|
||
assert.Error(t, err)
|
||
}
|
||
|
||
// ========== DissociateInbox ==========
|
||
|
||
func TestCaptainAssistantService_DissociateInbox_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
// 先关联
|
||
err := svc.AssociateInbox(context.Background(), assistant.ID, 100, account.ID)
|
||
require.NoError(t, err)
|
||
|
||
// 再取消关联
|
||
err = svc.DissociateInbox(context.Background(), assistant.ID, 100)
|
||
require.NoError(t, err)
|
||
}
|
||
|
||
func TestCaptainAssistantService_DissociateInbox_不存在关联(t *testing.T) {
|
||
_, _, svc := setupCaptainAssistantService(t)
|
||
|
||
err := svc.DissociateInbox(context.Background(), 9999, 100)
|
||
// GORM delete不存在的关联不报错
|
||
assert.NoError(t, err)
|
||
}
|
||
|
||
// ========== AddDocument ==========
|
||
|
||
func TestCaptainAssistantService_AddDocument_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
doc := &model.CaptainDocument{
|
||
AccountID: account.ID,
|
||
Name: "知识文档",
|
||
ExternalLink: "https://example.com/doc",
|
||
}
|
||
|
||
err := svc.AddDocument(context.Background(), assistant.ID, doc)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, assistant.ID, doc.AssistantID)
|
||
assert.NotZero(t, doc.ID)
|
||
}
|
||
|
||
func TestCaptainAssistantService_AddDocument_AssistantID被自动设置(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
doc := &model.CaptainDocument{
|
||
AccountID: account.ID,
|
||
Name: "验证文档",
|
||
ExternalLink: "https://example.com/verify",
|
||
}
|
||
|
||
err := svc.AddDocument(context.Background(), assistant.ID, doc)
|
||
require.NoError(t, err)
|
||
assert.Equal(t, assistant.ID, doc.AssistantID)
|
||
}
|
||
|
||
// ========== RemoveDocument ==========
|
||
|
||
func TestCaptainAssistantService_RemoveDocument_成功(t *testing.T) {
|
||
db, _, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
doc := createTestCaptainDocument(t, db, account.ID, assistant.ID, "测试文档", "https://example.com/test")
|
||
|
||
err := svc.RemoveDocument(context.Background(), doc.ID)
|
||
require.NoError(t, err)
|
||
|
||
// 验证已删除
|
||
var found model.CaptainDocument
|
||
err = db.First(&found, doc.ID).Error
|
||
assert.Error(t, err)
|
||
}
|
||
|
||
func TestCaptainAssistantService_RemoveDocument_不存在ID(t *testing.T) {
|
||
_, _, svc := setupCaptainAssistantService(t)
|
||
|
||
err := svc.RemoveDocument(context.Background(), 9999)
|
||
// GORM delete不存在记录不报错
|
||
assert.NoError(t, err)
|
||
}
|
||
|
||
// ========== GenerateResponse ==========
|
||
|
||
func TestCaptainAssistantService_GenerateResponse_成功(t *testing.T) {
|
||
db, mockProvider, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID, func(a *model.CaptainAssistant) {
|
||
cfg := &model.AssistantConfig{
|
||
Temperature: 0.7,
|
||
Model: "gpt-4",
|
||
ProductName: "测试产品",
|
||
}
|
||
data, _ := json.Marshal(cfg)
|
||
a.Config = data
|
||
})
|
||
if err := db.Save(assistant).Error; err != nil {
|
||
t.Fatalf("保存助手失败: %v", err)
|
||
}
|
||
|
||
// 设置mock LLM返回
|
||
mockProvider.chatResponse = &llm.ChatResponse{
|
||
Choices: []llm.ChatChoice{
|
||
{
|
||
Message: llm.ChatMessage{
|
||
Role: "assistant",
|
||
Content: "这是AI生成的回复",
|
||
},
|
||
},
|
||
},
|
||
}
|
||
|
||
result, err := svc.GenerateResponse(context.Background(), assistant.ID, "你好,请帮我解答一个问题")
|
||
require.NoError(t, err)
|
||
assert.Equal(t, "这是AI生成的回复", result)
|
||
|
||
// 验证LLM请求参数
|
||
assert.NotNil(t, mockProvider.lastChatRequest)
|
||
assert.Equal(t, "gpt-4", mockProvider.lastChatRequest.Model)
|
||
assert.Equal(t, 0.7, mockProvider.lastChatRequest.Temperature)
|
||
assert.Len(t, mockProvider.lastChatRequest.Messages, 2)
|
||
assert.Equal(t, "system", mockProvider.lastChatRequest.Messages[0].Role)
|
||
assert.Equal(t, "user", mockProvider.lastChatRequest.Messages[1].Role)
|
||
assert.Equal(t, "你好,请帮我解答一个问题", mockProvider.lastChatRequest.Messages[1].Content)
|
||
}
|
||
|
||
func TestCaptainAssistantService_GenerateResponse_助手不存在(t *testing.T) {
|
||
_, _, svc := setupCaptainAssistantService(t)
|
||
|
||
result, err := svc.GenerateResponse(context.Background(), 9999, "你好")
|
||
assert.Error(t, err)
|
||
assert.Empty(t, result)
|
||
}
|
||
|
||
func TestCaptainAssistantService_GenerateResponse_LLM调用失败(t *testing.T) {
|
||
db, mockProvider, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
mockProvider.chatError = fmt.Errorf("LLM API error")
|
||
|
||
result, err := svc.GenerateResponse(context.Background(), assistant.ID, "你好")
|
||
assert.Error(t, err)
|
||
assert.Contains(t, err.Error(), "llm generation failed")
|
||
assert.Empty(t, result)
|
||
}
|
||
|
||
func TestCaptainAssistantService_GenerateResponse_LLM返回空选项(t *testing.T) {
|
||
db, mockProvider, svc := setupCaptainAssistantService(t)
|
||
account := createTestAccount(t, db)
|
||
assistant := createTestCaptainAssistant(t, db, account.ID)
|
||
|
||
mockProvider.chatResponse = &llm.ChatResponse{
|
||
Choices: []llm.ChatChoice{},
|
||
}
|
||
|
||
result, err := svc.GenerateResponse(context.Background(), assistant.ID, "你好")
|
||
assert.Error(t, err)
|
||
assert.Contains(t, err.Error(), "no response from LLM")
|
||
assert.Empty(t, result)
|
||
} |