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