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

524 lines
16 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}