package service import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/gochat/gochat/internal/llm" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" ) // ========== Auto-Reply Rule Service Tests ========== func setupAutoReplyRuleService(t *testing.T) (*gorm.DB, *mockLLMProvider, *AutoReplyRuleService) { t.Helper() db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("failed to open test DB: %v", err) } if err := db.AutoMigrate( &model.Account{}, &model.Conversation{}, &model.Message{}, &model.CaptainAssistant{}, &model.CaptainAssistantResponse{}, &model.CaptainAutoReplyRule{}, ); err != nil { t.Fatalf("failed to auto-migrate: %v", err) } t.Cleanup(func() { sqlDB, _ := db.DB() sqlDB.Close() }) mockProvider := &mockLLMProvider{} assistantRepo := repository.NewCaptainAssistantRepo(db) ruleRepo := repository.NewCaptainAutoReplyRuleRepo(db) conversationRepo := repository.NewConversationRepo(db) svc := NewAutoReplyRuleService(ruleRepo, assistantRepo, conversationRepo, mockProvider) return db, mockProvider, svc } // --- CreateRule --- func TestAutoReplyRuleService_CreateRule(t *testing.T) { db, _, svc := setupAutoReplyRuleService(t) account := createTestAccount(t, db) assistant := createTestAssistant(t, db, account.ID, "TestAssistant") req := &CreateAutoReplyRuleRequest{ AssistantID: assistant.ID, Name: "UrgentHandler", Mode: "static", ResponseText: "We will respond to your urgent inquiry shortly.", Priority: 10, } result, err := svc.CreateRule(context.Background(), account.ID, req) require.NoError(t, err) assert.NotZero(t, result.ID) assert.Equal(t, "UrgentHandler", result.Name) assert.Equal(t, model.AutoReplyRuleStatusDraft, result.Status) assert.Equal(t, model.AutoReplyRuleModeStatic, result.Mode) } func TestAutoReplyRuleService_CreateRule_AssistantNotFound(t *testing.T) { db, _, svc := setupAutoReplyRuleService(t) account := createTestAccount(t, db) req := &CreateAutoReplyRuleRequest{ AssistantID: 9999, Name: "Test", Mode: "static", ResponseText: "test", } _, err := svc.CreateRule(context.Background(), account.ID, req) assert.Error(t, err) assert.Contains(t, err.Error(), "assistant not found") } func TestAutoReplyRuleService_CreateRule_WrongAccount(t *testing.T) { db, _, svc := setupAutoReplyRuleService(t) account1 := createTestAccountWithName(t, db, "Account1") account2 := createTestAccountWithName(t, db, "Account2") assistant := createTestAssistant(t, db, account1.ID, "TestAssistant") req := &CreateAutoReplyRuleRequest{ AssistantID: assistant.ID, Name: "Test", Mode: "static", ResponseText: "test", } _, err := svc.CreateRule(context.Background(), account2.ID, req) assert.Error(t, err) assert.Contains(t, err.Error(), "does not belong to account") } // --- GetRule --- func TestAutoReplyRuleService_GetRule(t *testing.T) { db, _, svc := setupAutoReplyRuleService(t) account := createTestAccount(t, db) assistant := createTestAssistant(t, db, account.ID, "TestAssistant") createReq := &CreateAutoReplyRuleRequest{ AssistantID: assistant.ID, Name: "TestRule", Mode: "static", ResponseText: "test response", } result, err := svc.CreateRule(context.Background(), account.ID, createReq) require.NoError(t, err) found, err := svc.GetRule(context.Background(), account.ID, result.ID) require.NoError(t, err) assert.Equal(t, result.ID, found.ID) assert.Equal(t, "TestRule", found.Name) } // --- UpdateRule --- func TestAutoReplyRuleService_UpdateRule(t *testing.T) { db, _, svc := setupAutoReplyRuleService(t) account := createTestAccount(t, db) assistant := createTestAssistant(t, db, account.ID, "TestAssistant") createReq := &CreateAutoReplyRuleRequest{ AssistantID: assistant.ID, Name: "OriginalName", Mode: "static", ResponseText: "original response", } result, err := svc.CreateRule(context.Background(), account.ID, createReq) require.NoError(t, err) newName := "UpdatedName" newStatus := model.AutoReplyRuleStatusActive updateReq := &UpdateAutoReplyRuleRequest{ Name: &newName, Status: &newStatus, } updated, err := svc.UpdateRule(context.Background(), account.ID, result.ID, updateReq) require.NoError(t, err) assert.Equal(t, "UpdatedName", updated.Name) assert.Equal(t, model.AutoReplyRuleStatusActive, updated.Status) } // --- DeleteRule --- func TestAutoReplyRuleService_DeleteRule(t *testing.T) { db, _, svc := setupAutoReplyRuleService(t) account := createTestAccount(t, db) assistant := createTestAssistant(t, db, account.ID, "TestAssistant") createReq := &CreateAutoReplyRuleRequest{ AssistantID: assistant.ID, Name: "ToDelete", Mode: "static", ResponseText: "test", } result, err := svc.CreateRule(context.Background(), account.ID, createReq) require.NoError(t, err) err = svc.DeleteRule(context.Background(), account.ID, result.ID) require.NoError(t, err) _, err = svc.GetRule(context.Background(), account.ID, result.ID) assert.Error(t, err) } // --- ListRules --- func TestAutoReplyRuleService_ListRules(t *testing.T) { db, _, svc := setupAutoReplyRuleService(t) account := createTestAccount(t, db) assistant := createTestAssistant(t, db, account.ID, "TestAssistant") for i := 0; i < 3; i++ { req := &CreateAutoReplyRuleRequest{ AssistantID: assistant.ID, Name: "Rule" + string(rune('A'+i)), Mode: "static", ResponseText: "response " + string(rune('A'+i)), } _, err := svc.CreateRule(context.Background(), account.ID, req) require.NoError(t, err) } rules, count, err := svc.ListRules(context.Background(), account.ID, 0, 10) require.NoError(t, err) assert.Equal(t, int64(3), count) assert.Len(t, rules, 3) } // --- EvaluateRules (condition matching) --- func TestAutoReplyRuleService_EvaluateRules_ContainsKeyword(t *testing.T) { db, mockProvider, svc := setupAutoReplyRuleService(t) account := createTestAccount(t, db) assistant := createTestAssistant(t, db, account.ID, "TestAssistant") req := &CreateAutoReplyRuleRequest{ AssistantID: assistant.ID, Name: "UrgentHandler", Mode: "static", ResponseText: "We will respond to your urgent inquiry shortly.", Priority: 10, Conditions: []model.AutoReplyCondition{ {Field: "message_content", Operator: "contains", Value: "urgent"}, }, } rule, err := svc.CreateRule(context.Background(), account.ID, req) require.NoError(t, err) // Activate the rule newStatus := model.AutoReplyRuleStatusActive _, err = svc.UpdateRule(context.Background(), account.ID, rule.ID, &UpdateAutoReplyRuleRequest{Status: &newStatus}) require.NoError(t, err) evalCtx := &AutoReplyEvaluationContext{ AccountID: account.ID, InboxID: 1, ConversationID: 1, MessageContent: "This is an urgent issue!", SenderType: "contact", } result, err := svc.EvaluateRules(context.Background(), evalCtx) require.NoError(t, err) assert.True(t, result.ShouldReply) assert.Equal(t, "We will respond to your urgent inquiry shortly.", result.ReplyContent) assert.Equal(t, model.AutoReplyRuleModeStatic, result.ReplyMode) // Mock provider should not have been called (static mode) assert.Nil(t, mockProvider.lastChatRequest) } func TestAutoReplyRuleService_EvaluateRules_NoMatch(t *testing.T) { db, _, svc := setupAutoReplyRuleService(t) account := createTestAccount(t, db) assistant := createTestAssistant(t, db, account.ID, "TestAssistant") req := &CreateAutoReplyRuleRequest{ AssistantID: assistant.ID, Name: "UrgentHandler", Mode: "static", ResponseText: "Urgent response", Priority: 10, Conditions: []model.AutoReplyCondition{ {Field: "message_content", Operator: "contains", Value: "urgent"}, }, } rule, err := svc.CreateRule(context.Background(), account.ID, req) require.NoError(t, err) newStatus := model.AutoReplyRuleStatusActive _, err = svc.UpdateRule(context.Background(), account.ID, rule.ID, &UpdateAutoReplyRuleRequest{Status: &newStatus}) require.NoError(t, err) evalCtx := &AutoReplyEvaluationContext{ AccountID: account.ID, InboxID: 1, ConversationID: 1, MessageContent: "Just a casual question about pricing", SenderType: "contact", } result, err := svc.EvaluateRules(context.Background(), evalCtx) require.NoError(t, err) assert.False(t, result.ShouldReply) } func TestAutoReplyRuleService_EvaluateRules_LLMMode(t *testing.T) { db, mockProvider, svc := setupAutoReplyRuleService(t) account := createTestAccount(t, db) assistant := createTestAssistant(t, db, account.ID, "TestAssistant") req := &CreateAutoReplyRuleRequest{ AssistantID: assistant.ID, Name: "LLMHandler", Mode: "llm", Priority: 10, Conditions: []model.AutoReplyCondition{ {Field: "message_content", Operator: "contains", Value: "help"}, }, } rule, err := svc.CreateRule(context.Background(), account.ID, req) require.NoError(t, err) newStatus := model.AutoReplyRuleStatusActive _, err = svc.UpdateRule(context.Background(), account.ID, rule.ID, &UpdateAutoReplyRuleRequest{Status: &newStatus}) require.NoError(t, err) // Setup mock LLM response mockProvider.chatResponse = &llm.ChatResponse{ Choices: []llm.ChatChoice{ {Message: llm.ChatMessage{Role: "assistant", Content: "Thank you for contacting us. We will review your request shortly."}}, }, } evalCtx := &AutoReplyEvaluationContext{ AccountID: account.ID, InboxID: 1, ConversationID: 1, MessageContent: "Hello, I need help with my account", SenderType: "contact", } result, err := svc.EvaluateRules(context.Background(), evalCtx) require.NoError(t, err) assert.True(t, result.ShouldReply) assert.Equal(t, model.AutoReplyRuleModeLLM, result.ReplyMode) assert.Contains(t, result.ReplyContent, "Thank you") assert.NotNil(t, mockProvider.lastChatRequest) }