package service import ( "context" "fmt" "testing" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" "github.com/gochat/gochat/internal/auth" "github.com/gochat/gochat/internal/config" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" "github.com/gochat/gochat/pkg/crypto" ) // setupBenchmarkDB creates an isolated in-memory SQLite database for benchmarks. func setupBenchmarkDB(b *testing.B) *gorm.DB { b.Helper() db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { b.Fatalf("failed to open benchmark db: %v", err) } if err := db.AutoMigrate( &model.Account{}, &model.User{}, &model.Inbox{}, &model.Contact{}, &model.Conversation{}, &model.Message{}, &model.Notification{}, &model.NotificationPreference{}, ); err != nil { b.Fatalf("failed to migrate: %v", err) } return db } // createBenchmarkAccount creates a test account for benchmarks. func createBenchmarkAccount(b *testing.B, db *gorm.DB, name string) *model.Account { b.Helper() account := &model.Account{Name: name, Locale: "en", Active: true} if err := db.Create(account).Error; err != nil { b.Fatalf("failed to create account: %v", err) } return account } // createBenchmarkUser creates a test user for benchmarks. func createBenchmarkUser(b *testing.B, db *gorm.DB, accountID uint, email string) *model.User { b.Helper() password, err := crypto.HashPassword("BenchmarkPass123!") if err != nil { b.Fatalf("failed to hash password: %v", err) } user := &model.User{ AccountID: accountID, Name: "Benchmark User", Email: email, Password: password, Role: "agent", Active: true, } if err := db.Create(user).Error; err != nil { b.Fatalf("failed to create user: %v", err) } return user } // createBenchmarkInbox creates a test inbox for benchmarks. func createBenchmarkInbox(b *testing.B, db *gorm.DB, accountID uint, name string) *model.Inbox { b.Helper() inbox := &model.Inbox{ AccountID: accountID, Name: name, ChannelType: "web_widget", EnableAutoAssignment: false, } if err := db.Create(inbox).Error; err != nil { b.Fatalf("failed to create inbox: %v", err) } return inbox } // createBenchmarkContact creates a test contact for benchmarks. func createBenchmarkContact(b *testing.B, db *gorm.DB, accountID uint, email string) *model.Contact { b.Helper() contact := &model.Contact{ AccountID: accountID, Name: "Benchmark Contact", Email: email, } if err := db.Create(contact).Error; err != nil { b.Fatalf("failed to create contact: %v", err) } return contact } // ============================================================================ // Auth Service Benchmarks // ============================================================================ // BenchmarkAuthServiceLogin benchmarks the full auth login flow (DB lookup + password verify). // Reference: Chatwoot's Devise login — Ruby bcrypt + ActiveRecord query takes ~50-80ms. func BenchmarkAuthServiceLogin(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Auth Login Bench Org") password, err := crypto.HashPassword("BenchmarkPass123!") if err != nil { b.Fatal(err) } user := &model.User{ AccountID: account.ID, Name: "Benchmark User", Email: "bench_auth_login@test.com", Password: password, Role: "agent", Active: true, } if err := db.Create(user).Error; err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { // Simulate login: find user + verify password var found model.User db.Where("email = ?", "bench_auth_login@test.com").First(&found) crypto.CheckPassword("BenchmarkPass123!", found.Password) } } // BenchmarkAuthServicePasswordHashing benchmarks bcrypt password hashing (expensive operation). // Reference: Chatwoot Devise bcrypt — Ruby takes ~100-200ms per hash (cost=10). func BenchmarkAuthServicePasswordHashing(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { _, err := crypto.HashPassword("benchmark_password_123!") if err != nil { b.Fatal(err) } } } // BenchmarkAuthServicePasswordVerification benchmarks bcrypt password check. // Reference: Chatwoot Devise bcrypt verify — Ruby takes ~50-100ms per verify. func BenchmarkAuthServicePasswordVerification(b *testing.B) { hash, err := crypto.HashPassword("benchmark_password_123!") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { crypto.CheckPassword("benchmark_password_123!", hash) } } // ============================================================================ // JWT Token Benchmarks // ============================================================================ // BenchmarkJWTGenerateTokenPair benchmarks JWT token pair generation (access + refresh). // Reference: Chatwoot DeviseTokenAuth — Ruby JWT generation takes ~5-15ms. func BenchmarkJWTGenerateTokenPair(b *testing.B) { cfg := &config.JWTConfig{ Secret: "benchmark-secret-key-for-testing", ExpiryHours: 1, RefreshExpiryHours: 168, } svc := auth.NewJWTService(cfg) user := &model.User{ Base: model.Base{ID: 42}, Name: "Benchmark Agent", Email: "bench_jwt@test.com", Provider: "email", Role: "agent", } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := svc.GenerateTokenPair(user, 1, "agent") if err != nil { b.Fatal(err) } } } // BenchmarkJWTValidateAccessToken benchmarks JWT access token validation. // Reference: Chatwoot DeviseTokenAuth token validation — Ruby takes ~3-10ms. func BenchmarkJWTValidateAccessToken(b *testing.B) { cfg := &config.JWTConfig{ Secret: "benchmark-secret-key-for-testing", ExpiryHours: 1, RefreshExpiryHours: 168, } svc := auth.NewJWTService(cfg) user := &model.User{ Base: model.Base{ID: 42}, Name: "Benchmark Agent", Email: "bench_jwt@test.com", Provider: "email", Role: "agent", } pair, err := svc.GenerateTokenPair(user, 1, "agent") if err != nil { b.Fatal(err) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := svc.ValidateAccessToken(pair.AccessToken) if err != nil { b.Fatal(err) } } } // ============================================================================ // Conversation Service Benchmarks // ============================================================================ // BenchmarkConversationService_Create benchmarks conversation creation via service. func BenchmarkConversationService_Create(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Conv Svc Bench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Conv Svc Bench Inbox") contact := createBenchmarkContact(b, db, account.ID, "conv_svc@contact.com") convRepo := repository.NewConversationRepo(db) msgRepo := repository.NewMessageRepo(db) svc := NewConversationService(convRepo, msgRepo, nil) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { _, err := svc.Create(ctx, account.ID, CreateConversationRequest{ InboxID: inbox.ID, ContactID: contact.ID, Status: "open", }) if err != nil { b.Fatal(err) } } } // BenchmarkConversationService_ListByAccount benchmarks conversation listing via service. func BenchmarkConversationService_ListByAccount(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Conv List Svc Bench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Conv List Svc Bench Inbox") contact := createBenchmarkContact(b, db, account.ID, "conv_list_svc@contact.com") convRepo := repository.NewConversationRepo(db) msgRepo := repository.NewMessageRepo(db) svc := NewConversationService(convRepo, msgRepo, nil) ctx := context.Background() // Pre-create 50 conversations for i := 0; i < 50; i++ { svc.Create(ctx, account.ID, CreateConversationRequest{ InboxID: inbox.ID, ContactID: contact.ID, Status: "open", }) } b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := svc.ListByAccount(ctx, account.ID, 0, 25) if err != nil { b.Fatal(err) } } } // ============================================================================ // Message Service Benchmarks // ============================================================================ // BenchmarkMessageService_Send benchmarks message creation via service. func BenchmarkMessageService_Send(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Msg Svc Bench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Msg Svc Bench Inbox") contact := createBenchmarkContact(b, db, account.ID, "msg_svc@contact.com") conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := db.Create(conv).Error; err != nil { b.Fatal(err) } user := createBenchmarkUser(b, db, account.ID, "msg_svc_user@test.com") msgRepo := repository.NewMessageRepo(db) svc := NewMessageService(msgRepo, nil) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { _, err := svc.Create(ctx, account.ID, user.ID, CreateMessageRequest{ ConversationID: conv.ID, Content: fmt.Sprintf("Benchmark send message %d", i), MessageType: "outgoing", ContentType: "text", }) if err != nil { b.Fatal(err) } } } // BenchmarkMessageService_ListByConversation benchmarks message listing via service. func BenchmarkMessageService_ListByConversation(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Msg List Svc Bench Org") inbox := createBenchmarkInbox(b, db, account.ID, "Msg List Svc Bench Inbox") contact := createBenchmarkContact(b, db, account.ID, "msg_list_svc@contact.com") conv := &model.Conversation{ AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget", } if err := db.Create(conv).Error; err != nil { b.Fatal(err) } user := createBenchmarkUser(b, db, account.ID, "msg_list_svc_user@test.com") msgRepo := repository.NewMessageRepo(db) svc := NewMessageService(msgRepo, nil) ctx := context.Background() // Pre-create 50 messages for i := 0; i < 50; i++ { svc.Create(ctx, account.ID, user.ID, CreateMessageRequest{ ConversationID: conv.ID, Content: fmt.Sprintf("Pre-created message %d", i), MessageType: "outgoing", ContentType: "text", }) } b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := svc.ListByConversation(ctx, conv.ID, 0, 25) if err != nil { b.Fatal(err) } } } // ============================================================================ // Notification Service Benchmarks // ============================================================================ // BenchmarkNotificationService_Create benchmarks notification creation via service. func BenchmarkNotificationService_Create(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Notif Svc Bench Org") user := createBenchmarkUser(b, db, account.ID, "notif_svc@test.com") notifRepo := repository.NewNotificationRepo(db) prefRepo := repository.NewNotificationPreferenceRepo(db) svc := NewNotificationService(db, notifRepo, prefRepo) ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { notif := &model.Notification{ AccountID: &account.ID, UserID: user.ID, NotificationType: "conversation_created", PrimaryActorType: "Conversation", PrimaryActorID: uint(i % 1000), } if err := svc.CreateNotification(ctx, notif); err != nil { b.Fatal(err) } } } // BenchmarkNotificationService_ListByUser benchmarks notification listing via service. func BenchmarkNotificationService_ListByUser(b *testing.B) { db := setupBenchmarkDB(b) account := createBenchmarkAccount(b, db, "Notif List Svc Bench Org") user := createBenchmarkUser(b, db, account.ID, "notif_list_svc@test.com") notifRepo := repository.NewNotificationRepo(db) prefRepo := repository.NewNotificationPreferenceRepo(db) svc := NewNotificationService(db, notifRepo, prefRepo) ctx := context.Background() // Pre-create 50 notifications for i := 0; i < 50; i++ { notif := &model.Notification{ AccountID: &account.ID, UserID: user.ID, NotificationType: "conversation_created", PrimaryActorType: "Conversation", PrimaryActorID: uint(i), } svc.CreateNotification(ctx, notif) } b.ResetTimer() for i := 0; i < b.N; i++ { _, _, err := svc.ListNotifications(ctx, user.ID, 1, 25) if err != nil { b.Fatal(err) } } }